github.com/ubuntu-core/snappy@v0.0.0-20210827154228-9e584df982bb/boot/boot_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-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 "errors" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "testing" 29 30 . "gopkg.in/check.v1" 31 32 "github.com/snapcore/snapd/asserts" 33 "github.com/snapcore/snapd/boot" 34 "github.com/snapcore/snapd/boot/boottest" 35 "github.com/snapcore/snapd/bootloader" 36 "github.com/snapcore/snapd/bootloader/bootloadertest" 37 "github.com/snapcore/snapd/dirs" 38 "github.com/snapcore/snapd/osutil" 39 "github.com/snapcore/snapd/secboot" 40 "github.com/snapcore/snapd/seed" 41 "github.com/snapcore/snapd/snap" 42 "github.com/snapcore/snapd/snap/snaptest" 43 "github.com/snapcore/snapd/testutil" 44 "github.com/snapcore/snapd/timings" 45 ) 46 47 func TestBoot(t *testing.T) { TestingT(t) } 48 49 type baseBootenvSuite struct { 50 testutil.BaseTest 51 52 rootdir string 53 bootdir string 54 cmdlineFile string 55 } 56 57 func (s *baseBootenvSuite) SetUpTest(c *C) { 58 s.BaseTest.SetUpTest(c) 59 60 s.rootdir = c.MkDir() 61 dirs.SetRootDir(s.rootdir) 62 s.AddCleanup(func() { dirs.SetRootDir("") }) 63 restore := snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}) 64 s.AddCleanup(restore) 65 66 s.bootdir = filepath.Join(s.rootdir, "boot") 67 68 s.cmdlineFile = filepath.Join(c.MkDir(), "cmdline") 69 restore = osutil.MockProcCmdline(s.cmdlineFile) 70 s.AddCleanup(restore) 71 } 72 73 func (s *baseBootenvSuite) forceBootloader(bloader bootloader.Bootloader) { 74 bootloader.Force(bloader) 75 s.AddCleanup(func() { bootloader.Force(nil) }) 76 } 77 78 func (s *baseBootenvSuite) stampSealedKeys(c *C, rootdir string) { 79 stamp := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "sealed-keys") 80 c.Assert(os.MkdirAll(filepath.Dir(stamp), 0755), IsNil) 81 err := ioutil.WriteFile(stamp, nil, 0644) 82 c.Assert(err, IsNil) 83 } 84 85 func (s *baseBootenvSuite) mockCmdline(c *C, cmdline string) { 86 c.Assert(ioutil.WriteFile(s.cmdlineFile, []byte(cmdline), 0644), IsNil) 87 } 88 89 // mockAssetsCache mocks the listed assets in the boot assets cache by creating 90 // an empty file for each. 91 func mockAssetsCache(c *C, rootdir, bootloaderName string, cachedAssets []string) { 92 p := filepath.Join(dirs.SnapBootAssetsDirUnder(rootdir), bootloaderName) 93 err := os.MkdirAll(p, 0755) 94 c.Assert(err, IsNil) 95 for _, cachedAsset := range cachedAssets { 96 err = ioutil.WriteFile(filepath.Join(p, cachedAsset), nil, 0644) 97 c.Assert(err, IsNil) 98 } 99 } 100 101 type bootenvSuite struct { 102 baseBootenvSuite 103 104 bootloader *bootloadertest.MockBootloader 105 } 106 107 var _ = Suite(&bootenvSuite{}) 108 109 func (s *bootenvSuite) SetUpTest(c *C) { 110 s.baseBootenvSuite.SetUpTest(c) 111 112 s.bootloader = bootloadertest.Mock("mock", c.MkDir()) 113 s.forceBootloader(s.bootloader) 114 } 115 116 type baseBootenv20Suite struct { 117 baseBootenvSuite 118 119 kern1 snap.PlaceInfo 120 kern2 snap.PlaceInfo 121 ukern1 snap.PlaceInfo 122 ukern2 snap.PlaceInfo 123 base1 snap.PlaceInfo 124 base2 snap.PlaceInfo 125 126 normalDefaultState *bootenv20Setup 127 normalTryingKernelState *bootenv20Setup 128 } 129 130 func (s *baseBootenv20Suite) SetUpTest(c *C) { 131 s.baseBootenvSuite.SetUpTest(c) 132 133 var err error 134 s.kern1, err = snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap") 135 c.Assert(err, IsNil) 136 s.kern2, err = snap.ParsePlaceInfoFromSnapFileName("pc-kernel_2.snap") 137 c.Assert(err, IsNil) 138 139 s.ukern1, err = snap.ParsePlaceInfoFromSnapFileName("pc-kernel_x1.snap") 140 c.Assert(err, IsNil) 141 s.ukern2, err = snap.ParsePlaceInfoFromSnapFileName("pc-kernel_x2.snap") 142 c.Assert(err, IsNil) 143 144 s.base1, err = snap.ParsePlaceInfoFromSnapFileName("core20_1.snap") 145 c.Assert(err, IsNil) 146 s.base2, err = snap.ParsePlaceInfoFromSnapFileName("core20_2.snap") 147 c.Assert(err, IsNil) 148 149 // default boot state for robustness tests, etc. 150 s.normalDefaultState = &bootenv20Setup{ 151 modeenv: &boot.Modeenv{ 152 // base is base1 153 Base: s.base1.Filename(), 154 // no try base 155 TryBase: "", 156 // base status is default 157 BaseStatus: boot.DefaultStatus, 158 // current kernels is just kern1 159 CurrentKernels: []string{s.kern1.Filename()}, 160 // operating mode is run 161 Mode: "run", 162 // RecoverySystem is unset, as it should be during run mode 163 RecoverySystem: "", 164 }, 165 // enabled kernel is kern1 166 kern: s.kern1, 167 // no try kernel enabled 168 tryKern: nil, 169 // kernel status is default 170 kernStatus: boot.DefaultStatus, 171 } 172 173 // state for after trying a new kernel for robustness tests, etc. 174 s.normalTryingKernelState = &bootenv20Setup{ 175 modeenv: &boot.Modeenv{ 176 // operating mode is run 177 Mode: "run", 178 // base is base1 179 Base: s.base1.Filename(), 180 // no try base 181 TryBase: "", 182 // base status is default 183 BaseStatus: boot.DefaultStatus, 184 // current kernels is kern1 + kern2 185 CurrentKernels: []string{s.kern1.Filename(), s.kern2.Filename()}, 186 }, 187 // enabled kernel is kern1 188 kern: s.kern1, 189 // try kernel is kern2 190 tryKern: s.kern2, 191 // kernel status is trying 192 kernStatus: boot.TryingStatus, 193 } 194 195 s.mockCmdline(c, "snapd_recovery_mode=run") 196 } 197 198 type bootenv20Suite struct { 199 baseBootenv20Suite 200 201 bootloader *bootloadertest.MockExtractedRunKernelImageBootloader 202 } 203 204 type bootenv20EnvRefKernelSuite struct { 205 baseBootenv20Suite 206 207 bootloader *bootloadertest.MockBootloader 208 } 209 210 var _ = Suite(&bootenv20Suite{}) 211 var _ = Suite(&bootenv20EnvRefKernelSuite{}) 212 213 func (s *bootenv20Suite) SetUpTest(c *C) { 214 s.baseBootenv20Suite.SetUpTest(c) 215 216 s.bootloader = bootloadertest.Mock("mock", c.MkDir()).WithExtractedRunKernelImage() 217 s.forceBootloader(s.bootloader) 218 } 219 220 func (s *bootenv20EnvRefKernelSuite) SetUpTest(c *C) { 221 s.baseBootenv20Suite.SetUpTest(c) 222 223 s.bootloader = bootloadertest.Mock("mock", c.MkDir()) 224 s.forceBootloader(s.bootloader) 225 } 226 227 type bootenv20Setup struct { 228 modeenv *boot.Modeenv 229 kern snap.PlaceInfo 230 tryKern snap.PlaceInfo 231 kernStatus string 232 } 233 234 func setupUC20Bootenv(c *C, bl bootloader.Bootloader, opts *bootenv20Setup) (restore func()) { 235 var cleanups []func() 236 237 // write the modeenv 238 if opts.modeenv != nil { 239 c.Assert(opts.modeenv.WriteTo(""), IsNil) 240 // this isn't strictly necessary since the modeenv will be written to 241 // the test's private dir anyways, but it's nice to have so we can write 242 // multiple modeenvs from a single test and just call the restore 243 // function in between the parts of the test that use different modeenvs 244 r := func() { 245 defaultModeenv := &boot.Modeenv{Mode: "run"} 246 c.Assert(defaultModeenv.WriteTo(""), IsNil) 247 } 248 cleanups = append(cleanups, r) 249 } 250 251 // set the status 252 origEnv, err := bl.GetBootVars("kernel_status") 253 c.Assert(err, IsNil) 254 255 err = bl.SetBootVars(map[string]string{"kernel_status": opts.kernStatus}) 256 c.Assert(err, IsNil) 257 cleanups = append(cleanups, func() { 258 err := bl.SetBootVars(origEnv) 259 c.Assert(err, IsNil) 260 }) 261 262 // check what kind of real mock bootloader we have to use different methods 263 // to set the kernel snaps are if they're non-nil 264 switch vbl := bl.(type) { 265 case *bootloadertest.MockExtractedRunKernelImageBootloader: 266 // then we can use the advanced methods on it 267 if opts.kern != nil { 268 r := vbl.SetEnabledKernel(opts.kern) 269 cleanups = append(cleanups, r) 270 } 271 272 if opts.tryKern != nil { 273 r := vbl.SetEnabledTryKernel(opts.tryKern) 274 cleanups = append(cleanups, r) 275 } 276 277 // don't count any calls to SetBootVars made thus far 278 vbl.SetBootVarsCalls = 0 279 280 case *bootloadertest.MockBootloader: 281 // then we need to use the bootenv to set the current kernels 282 origEnv, err := vbl.GetBootVars("snap_kernel", "snap_try_kernel") 283 c.Assert(err, IsNil) 284 m := make(map[string]string, 2) 285 if opts.kern != nil { 286 m["snap_kernel"] = opts.kern.Filename() 287 } else { 288 m["snap_kernel"] = "" 289 } 290 291 if opts.tryKern != nil { 292 m["snap_try_kernel"] = opts.tryKern.Filename() 293 } else { 294 m["snap_try_kernel"] = "" 295 } 296 297 err = vbl.SetBootVars(m) 298 c.Assert(err, IsNil) 299 300 // don't count any calls to SetBootVars made thus far 301 vbl.SetBootVarsCalls = 0 302 303 cleanups = append(cleanups, func() { 304 err := bl.SetBootVars(origEnv) 305 c.Assert(err, IsNil) 306 }) 307 default: 308 c.Fatalf("unsupported bootloader %T", bl) 309 } 310 311 return func() { 312 for _, r := range cleanups { 313 r() 314 } 315 } 316 } 317 318 func (s *bootenvSuite) TestInUseClassic(c *C) { 319 classicDev := boottest.MockDevice("") 320 321 // make bootloader.Find fail but shouldn't matter 322 bootloader.ForceError(errors.New("broken bootloader")) 323 324 inUse, err := boot.InUse(snap.TypeBase, classicDev) 325 c.Assert(err, IsNil) 326 c.Check(inUse("core18", snap.R(41)), Equals, false) 327 } 328 329 func (s *bootenvSuite) TestInUseIrrelevantTypes(c *C) { 330 coreDev := boottest.MockDevice("some-snap") 331 332 // make bootloader.Find fail but shouldn't matter 333 bootloader.ForceError(errors.New("broken bootloader")) 334 335 inUse, err := boot.InUse(snap.TypeGadget, coreDev) 336 c.Assert(err, IsNil) 337 c.Check(inUse("gadget", snap.R(41)), Equals, false) 338 } 339 340 func (s *bootenvSuite) TestInUse(c *C) { 341 coreDev := boottest.MockDevice("some-snap") 342 343 for _, t := range []struct { 344 bootVarKey string 345 bootVarValue string 346 347 snapName string 348 snapRev snap.Revision 349 350 inUse bool 351 }{ 352 // in use 353 {"snap_kernel", "kernel_41.snap", "kernel", snap.R(41), true}, 354 {"snap_try_kernel", "kernel_82.snap", "kernel", snap.R(82), true}, 355 {"snap_core", "core_21.snap", "core", snap.R(21), true}, 356 {"snap_try_core", "core_42.snap", "core", snap.R(42), true}, 357 // not in use 358 {"snap_core", "core_111.snap", "core", snap.R(21), false}, 359 {"snap_try_core", "core_111.snap", "core", snap.R(21), false}, 360 {"snap_kernel", "kernel_111.snap", "kernel", snap.R(1), false}, 361 {"snap_try_kernel", "kernel_111.snap", "kernel", snap.R(1), false}, 362 } { 363 typ := snap.TypeBase 364 if t.snapName == "kernel" { 365 typ = snap.TypeKernel 366 } 367 s.bootloader.BootVars[t.bootVarKey] = t.bootVarValue 368 inUse, err := boot.InUse(typ, coreDev) 369 c.Assert(err, IsNil) 370 c.Assert(inUse(t.snapName, t.snapRev), Equals, t.inUse, Commentf("unexpected result: %s %s %v", t.snapName, t.snapRev, t.inUse)) 371 } 372 } 373 374 func (s *bootenvSuite) TestInUseEphemeral(c *C) { 375 coreDev := boottest.MockDevice("some-snap@install") 376 377 // make bootloader.Find fail but shouldn't matter 378 bootloader.ForceError(errors.New("broken bootloader")) 379 380 inUse, err := boot.InUse(snap.TypeBase, coreDev) 381 c.Assert(err, IsNil) 382 c.Check(inUse("whatever", snap.R(0)), Equals, true) 383 } 384 385 func (s *bootenvSuite) TestInUseUnhappy(c *C) { 386 coreDev := boottest.MockDevice("some-snap") 387 388 // make GetVars fail 389 s.bootloader.GetErr = errors.New("zap") 390 _, err := boot.InUse(snap.TypeKernel, coreDev) 391 c.Check(err, ErrorMatches, `cannot get boot variables: zap`) 392 393 // make bootloader.Find fail 394 bootloader.ForceError(errors.New("broken bootloader")) 395 _, err = boot.InUse(snap.TypeKernel, coreDev) 396 c.Check(err, ErrorMatches, `cannot get boot settings: broken bootloader`) 397 } 398 399 func (s *bootenvSuite) TestCurrentBootNameAndRevision(c *C) { 400 coreDev := boottest.MockDevice("some-snap") 401 402 s.bootloader.BootVars["snap_core"] = "core_2.snap" 403 s.bootloader.BootVars["snap_kernel"] = "canonical-pc-linux_2.snap" 404 405 current, err := boot.GetCurrentBoot(snap.TypeOS, coreDev) 406 c.Check(err, IsNil) 407 c.Check(current.SnapName(), Equals, "core") 408 c.Check(current.SnapRevision(), Equals, snap.R(2)) 409 410 current, err = boot.GetCurrentBoot(snap.TypeKernel, coreDev) 411 c.Check(err, IsNil) 412 c.Check(current.SnapName(), Equals, "canonical-pc-linux") 413 c.Check(current.SnapRevision(), Equals, snap.R(2)) 414 415 s.bootloader.BootVars["snap_mode"] = boot.TryingStatus 416 _, err = boot.GetCurrentBoot(snap.TypeKernel, coreDev) 417 c.Check(err, Equals, boot.ErrBootNameAndRevisionNotReady) 418 } 419 420 func (s *bootenv20Suite) TestCurrentBoot20NameAndRevision(c *C) { 421 coreDev := boottest.MockUC20Device("", nil) 422 c.Assert(coreDev.HasModeenv(), Equals, true) 423 424 r := setupUC20Bootenv( 425 c, 426 s.bootloader, 427 s.normalDefaultState, 428 ) 429 defer r() 430 431 current, err := boot.GetCurrentBoot(snap.TypeBase, coreDev) 432 c.Check(err, IsNil) 433 c.Check(current.SnapName(), Equals, s.base1.SnapName()) 434 c.Check(current.SnapRevision(), Equals, snap.R(1)) 435 436 current, err = boot.GetCurrentBoot(snap.TypeKernel, coreDev) 437 c.Check(err, IsNil) 438 c.Check(current.SnapName(), Equals, s.kern1.SnapName()) 439 c.Check(current.SnapRevision(), Equals, snap.R(1)) 440 441 s.bootloader.BootVars["kernel_status"] = boot.TryingStatus 442 _, err = boot.GetCurrentBoot(snap.TypeKernel, coreDev) 443 c.Check(err, Equals, boot.ErrBootNameAndRevisionNotReady) 444 } 445 446 // only difference between this test and TestCurrentBoot20NameAndRevision is the 447 // base bootloader which doesn't support ExtractedRunKernelImageBootloader. 448 func (s *bootenv20EnvRefKernelSuite) TestCurrentBoot20NameAndRevision(c *C) { 449 coreDev := boottest.MockUC20Device("", nil) 450 c.Assert(coreDev.HasModeenv(), Equals, true) 451 452 r := setupUC20Bootenv( 453 c, 454 s.bootloader, 455 s.normalDefaultState, 456 ) 457 defer r() 458 459 current, err := boot.GetCurrentBoot(snap.TypeKernel, coreDev) 460 c.Assert(err, IsNil) 461 c.Assert(current.SnapName(), Equals, s.kern1.SnapName()) 462 c.Assert(current.SnapRevision(), Equals, snap.R(1)) 463 } 464 465 func (s *bootenvSuite) TestCurrentBootNameAndRevisionUnhappy(c *C) { 466 coreDev := boottest.MockDevice("some-snap") 467 468 _, err := boot.GetCurrentBoot(snap.TypeKernel, coreDev) 469 c.Check(err, ErrorMatches, `cannot get name and revision of kernel \(snap_kernel\): boot variable unset`) 470 471 _, err = boot.GetCurrentBoot(snap.TypeOS, coreDev) 472 c.Check(err, ErrorMatches, `cannot get name and revision of boot base \(snap_core\): boot variable unset`) 473 474 _, err = boot.GetCurrentBoot(snap.TypeBase, coreDev) 475 c.Check(err, ErrorMatches, `cannot get name and revision of boot base \(snap_core\): boot variable unset`) 476 477 _, err = boot.GetCurrentBoot(snap.TypeApp, coreDev) 478 c.Check(err, ErrorMatches, `internal error: no boot state handling for snap type "app"`) 479 480 // sanity check 481 s.bootloader.BootVars["snap_kernel"] = "kernel_41.snap" 482 current, err := boot.GetCurrentBoot(snap.TypeKernel, coreDev) 483 c.Check(err, IsNil) 484 c.Check(current.SnapName(), Equals, "kernel") 485 c.Check(current.SnapRevision(), Equals, snap.R(41)) 486 487 // make GetVars fail 488 s.bootloader.GetErr = errors.New("zap") 489 _, err = boot.GetCurrentBoot(snap.TypeKernel, coreDev) 490 c.Check(err, ErrorMatches, "cannot get boot variables: zap") 491 s.bootloader.GetErr = nil 492 493 // make bootloader.Find fail 494 bootloader.ForceError(errors.New("broken bootloader")) 495 _, err = boot.GetCurrentBoot(snap.TypeKernel, coreDev) 496 c.Check(err, ErrorMatches, "cannot get boot settings: broken bootloader") 497 } 498 499 func (s *bootenvSuite) TestParticipant(c *C) { 500 info := &snap.Info{} 501 info.RealName = "some-snap" 502 503 coreDev := boottest.MockDevice("some-snap") 504 classicDev := boottest.MockDevice("") 505 506 bp := boot.Participant(info, snap.TypeApp, coreDev) 507 c.Check(bp.IsTrivial(), Equals, true) 508 509 for _, typ := range []snap.Type{ 510 snap.TypeKernel, 511 snap.TypeOS, 512 snap.TypeBase, 513 } { 514 bp = boot.Participant(info, typ, classicDev) 515 c.Check(bp.IsTrivial(), Equals, true) 516 517 bp = boot.Participant(info, typ, coreDev) 518 c.Check(bp.IsTrivial(), Equals, false) 519 520 c.Check(bp, DeepEquals, boot.NewCoreBootParticipant(info, typ, coreDev)) 521 } 522 } 523 524 func (s *bootenvSuite) TestParticipantBaseWithModel(c *C) { 525 core := &snap.Info{SideInfo: snap.SideInfo{RealName: "core"}, SnapType: snap.TypeOS} 526 core18 := &snap.Info{SideInfo: snap.SideInfo{RealName: "core18"}, SnapType: snap.TypeBase} 527 528 type tableT struct { 529 with *snap.Info 530 model string 531 nop bool 532 } 533 534 table := []tableT{ 535 { 536 with: core, 537 model: "", 538 nop: true, 539 }, { 540 with: core, 541 model: "core", 542 nop: false, 543 }, { 544 with: core, 545 model: "core18", 546 nop: true, 547 }, 548 { 549 with: core18, 550 model: "", 551 nop: true, 552 }, 553 { 554 with: core18, 555 model: "core", 556 nop: true, 557 }, 558 { 559 with: core18, 560 model: "core18", 561 nop: false, 562 }, 563 { 564 with: core18, 565 model: "core18@install", 566 nop: true, 567 }, 568 { 569 with: core, 570 model: "core@install", 571 nop: true, 572 }, 573 } 574 575 for i, t := range table { 576 dev := boottest.MockDevice(t.model) 577 bp := boot.Participant(t.with, t.with.Type(), dev) 578 c.Check(bp.IsTrivial(), Equals, t.nop, Commentf("%d", i)) 579 if !t.nop { 580 c.Check(bp, DeepEquals, boot.NewCoreBootParticipant(t.with, t.with.Type(), dev)) 581 } 582 } 583 } 584 585 func (s *bootenvSuite) TestKernelWithModel(c *C) { 586 info := &snap.Info{} 587 info.RealName = "kernel" 588 589 type tableT struct { 590 model string 591 nop bool 592 krn boot.BootKernel 593 } 594 595 table := []tableT{ 596 { 597 model: "other-kernel", 598 nop: true, 599 krn: boot.Trivial{}, 600 }, { 601 model: "kernel", 602 nop: false, 603 krn: boot.NewCoreKernel(info, boottest.MockDevice("kernel")), 604 }, { 605 model: "", 606 nop: true, 607 krn: boot.Trivial{}, 608 }, { 609 model: "kernel@install", 610 nop: true, 611 krn: boot.Trivial{}, 612 }, 613 } 614 615 for _, t := range table { 616 dev := boottest.MockDevice(t.model) 617 krn := boot.Kernel(info, snap.TypeKernel, dev) 618 c.Check(krn.IsTrivial(), Equals, t.nop) 619 c.Check(krn, DeepEquals, t.krn) 620 } 621 } 622 623 func (s *bootenvSuite) TestMarkBootSuccessfulKernelStatusTryingNoTryKernelSnapCleansUp(c *C) { 624 coreDev := boottest.MockDevice("some-snap") 625 626 // set all the same vars as if we were doing trying, except don't set a try 627 // kernel 628 629 err := s.bootloader.SetBootVars(map[string]string{ 630 "snap_kernel": "kernel_41.snap", 631 "snap_mode": boot.TryingStatus, 632 }) 633 c.Assert(err, IsNil) 634 635 // mark successful 636 err = boot.MarkBootSuccessful(coreDev) 637 c.Assert(err, IsNil) 638 639 // check that the bootloader variables were cleaned 640 expected := map[string]string{ 641 "snap_mode": boot.DefaultStatus, 642 "snap_kernel": "kernel_41.snap", 643 "snap_try_kernel": "", 644 } 645 m, err := s.bootloader.GetBootVars("snap_mode", "snap_try_kernel", "snap_kernel") 646 c.Assert(err, IsNil) 647 c.Assert(m, DeepEquals, expected) 648 649 // do it again, verify it's still okay 650 err = boot.MarkBootSuccessful(coreDev) 651 c.Assert(err, IsNil) 652 m2, err := s.bootloader.GetBootVars("snap_mode", "snap_try_kernel", "snap_kernel") 653 c.Assert(err, IsNil) 654 c.Assert(m2, DeepEquals, expected) 655 } 656 657 func (s *bootenvSuite) TestMarkBootSuccessfulTryKernelKernelStatusDefaultCleansUp(c *C) { 658 coreDev := boottest.MockDevice("some-snap") 659 660 // set an errant snap_try_kernel 661 err := s.bootloader.SetBootVars(map[string]string{ 662 "snap_kernel": "kernel_41.snap", 663 "snap_try_kernel": "kernel_42.snap", 664 "snap_mode": boot.DefaultStatus, 665 }) 666 c.Assert(err, IsNil) 667 668 // mark successful 669 err = boot.MarkBootSuccessful(coreDev) 670 c.Assert(err, IsNil) 671 672 // check that the bootloader variables were cleaned 673 expected := map[string]string{ 674 "snap_mode": boot.DefaultStatus, 675 "snap_kernel": "kernel_41.snap", 676 "snap_try_kernel": "", 677 } 678 m, err := s.bootloader.GetBootVars("snap_mode", "snap_try_kernel", "snap_kernel") 679 c.Assert(err, IsNil) 680 c.Assert(m, DeepEquals, expected) 681 682 // do it again, verify it's still okay 683 err = boot.MarkBootSuccessful(coreDev) 684 c.Assert(err, IsNil) 685 m2, err := s.bootloader.GetBootVars("snap_mode", "snap_try_kernel", "snap_kernel") 686 c.Assert(err, IsNil) 687 c.Assert(m2, DeepEquals, expected) 688 } 689 690 func (s *bootenv20Suite) TestCoreKernel20(c *C) { 691 coreDev := boottest.MockUC20Device("", nil) 692 c.Assert(coreDev.HasModeenv(), Equals, true) 693 694 r := setupUC20Bootenv( 695 c, 696 s.bootloader, 697 s.normalDefaultState, 698 ) 699 defer r() 700 701 // get the boot kernel from our kernel snap 702 bootKern := boot.Kernel(s.kern1, snap.TypeKernel, coreDev) 703 // can't use FitsTypeOf with coreKernel here, cause that causes an import 704 // loop as boottest imports boot and coreKernel is unexported 705 c.Assert(bootKern.IsTrivial(), Equals, false) 706 707 // extract the kernel assets from the coreKernel 708 // the container here doesn't really matter since it's just being passed 709 // to the mock bootloader method anyways 710 kernelContainer := snaptest.MockContainer(c, nil) 711 err := bootKern.ExtractKernelAssets(kernelContainer) 712 c.Assert(err, IsNil) 713 714 // make sure that the bootloader was told to extract some assets 715 c.Assert(s.bootloader.ExtractKernelAssetsCalls, DeepEquals, []snap.PlaceInfo{s.kern1}) 716 717 // now remove the kernel assets and ensure that we get those calls 718 err = bootKern.RemoveKernelAssets() 719 c.Assert(err, IsNil) 720 721 // make sure that the bootloader was told to remove assets 722 c.Assert(s.bootloader.RemoveKernelAssetsCalls, DeepEquals, []snap.PlaceInfo{s.kern1}) 723 } 724 725 func (s *bootenv20Suite) TestCoreParticipant20SetNextSameKernelSnap(c *C) { 726 coreDev := boottest.MockUC20Device("", nil) 727 c.Assert(coreDev.HasModeenv(), Equals, true) 728 729 r := setupUC20Bootenv( 730 c, 731 s.bootloader, 732 s.normalDefaultState, 733 ) 734 defer r() 735 736 // get the boot kernel participant from our kernel snap 737 bootKern := boot.Participant(s.kern1, snap.TypeKernel, coreDev) 738 739 // make sure it's not a trivial boot participant 740 c.Assert(bootKern.IsTrivial(), Equals, false) 741 742 // make the kernel used on next boot 743 rebootRequired, err := bootKern.SetNextBoot() 744 c.Assert(err, IsNil) 745 c.Assert(rebootRequired, Equals, false) 746 747 // make sure that the bootloader was asked for the current kernel 748 _, nKernelCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("Kernel") 749 c.Assert(nKernelCalls, Equals, 1) 750 751 // ensure that kernel_status is still empty 752 c.Assert(s.bootloader.BootVars["kernel_status"], Equals, boot.DefaultStatus) 753 754 // there was no attempt to enable a kernel 755 _, enableKernelCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableTryKernel") 756 c.Assert(enableKernelCalls, Equals, 0) 757 758 // the modeenv is still the same as well 759 m2, err := boot.ReadModeenv("") 760 c.Assert(err, IsNil) 761 c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename()}) 762 763 // finally we didn't call SetBootVars on the bootloader because nothing 764 // changed 765 c.Assert(s.bootloader.SetBootVarsCalls, Equals, 0) 766 } 767 768 func (s *bootenv20EnvRefKernelSuite) TestCoreParticipant20SetNextSameKernelSnap(c *C) { 769 coreDev := boottest.MockUC20Device("", nil) 770 c.Assert(coreDev.HasModeenv(), Equals, true) 771 772 r := setupUC20Bootenv( 773 c, 774 s.bootloader, 775 s.normalDefaultState, 776 ) 777 defer r() 778 779 // get the boot kernel participant from our kernel snap 780 bootKern := boot.Participant(s.kern1, snap.TypeKernel, coreDev) 781 782 // make sure it's not a trivial boot participant 783 c.Assert(bootKern.IsTrivial(), Equals, false) 784 785 // make the kernel used on next boot 786 rebootRequired, err := bootKern.SetNextBoot() 787 c.Assert(err, IsNil) 788 c.Assert(rebootRequired, Equals, false) 789 790 // ensure that bootenv is unchanged 791 m, err := s.bootloader.GetBootVars("kernel_status", "snap_kernel", "snap_try_kernel") 792 c.Assert(err, IsNil) 793 c.Assert(m, DeepEquals, map[string]string{ 794 "kernel_status": boot.DefaultStatus, 795 "snap_kernel": s.kern1.Filename(), 796 "snap_try_kernel": "", 797 }) 798 799 // the modeenv is still the same as well 800 m2, err := boot.ReadModeenv("") 801 c.Assert(err, IsNil) 802 c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename()}) 803 804 // finally we didn't call SetBootVars on the bootloader because nothing 805 // changed 806 c.Assert(s.bootloader.SetBootVarsCalls, Equals, 0) 807 } 808 809 func (s *bootenv20Suite) TestCoreParticipant20SetNextNewKernelSnap(c *C) { 810 coreDev := boottest.MockUC20Device("", nil) 811 c.Assert(coreDev.HasModeenv(), Equals, true) 812 813 r := setupUC20Bootenv( 814 c, 815 s.bootloader, 816 s.normalDefaultState, 817 ) 818 defer r() 819 820 // get the boot kernel participant from our new kernel snap 821 bootKern := boot.Participant(s.kern2, snap.TypeKernel, coreDev) 822 // make sure it's not a trivial boot participant 823 c.Assert(bootKern.IsTrivial(), Equals, false) 824 825 // make the kernel used on next boot 826 rebootRequired, err := bootKern.SetNextBoot() 827 c.Assert(err, IsNil) 828 c.Assert(rebootRequired, Equals, true) 829 830 // make sure that the bootloader was asked for the current kernel 831 _, nKernelCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("Kernel") 832 c.Assert(nKernelCalls, Equals, 1) 833 834 // ensure that kernel_status is now try 835 c.Assert(s.bootloader.BootVars["kernel_status"], Equals, boot.TryStatus) 836 837 // and we were asked to enable kernel2 as the try kernel 838 actual, _ := s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableTryKernel") 839 c.Assert(actual, DeepEquals, []snap.PlaceInfo{s.kern2}) 840 841 // and that the modeenv now has this kernel listed 842 m2, err := boot.ReadModeenv("") 843 c.Assert(err, IsNil) 844 c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename(), s.kern2.Filename()}) 845 } 846 847 func (s *bootenv20Suite) TestCoreParticipant20SetNextNewKernelSnapWithReseal(c *C) { 848 // checked by resealKeyToModeenv 849 s.stampSealedKeys(c, dirs.GlobalRootDir) 850 851 tab := s.bootloaderWithTrustedAssets(c, []string{"asset"}) 852 853 data := []byte("foobar") 854 // SHA3-384 855 dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8" 856 857 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil) 858 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir), 0755), IsNil) 859 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil) 860 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil) 861 862 // mock the files in cache 863 mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{ 864 "asset-" + dataHash, 865 }) 866 867 assetBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)), bootloader.RoleRunMode) 868 runKernelBf := bootloader.NewBootFile(filepath.Join(s.kern1.Filename()), "kernel.efi", bootloader.RoleRunMode) 869 870 tab.BootChainList = []bootloader.BootFile{ 871 bootloader.NewBootFile("", "asset", bootloader.RoleRunMode), 872 // TODO:UC20: fix mocked trusted assets bootloader to actually 873 // geenerate kernel boot files 874 runKernelBf, 875 } 876 877 coreDev := boottest.MockUC20Device("", nil) 878 c.Assert(coreDev.HasModeenv(), Equals, true) 879 model := coreDev.Model() 880 881 m := &boot.Modeenv{ 882 Mode: "run", 883 Base: s.base1.Filename(), 884 CurrentKernels: []string{s.kern1.Filename()}, 885 CurrentTrustedBootAssets: boot.BootAssetsMap{ 886 "asset": {dataHash}, 887 }, 888 Model: model.Model(), 889 BrandID: model.BrandID(), 890 Grade: string(model.Grade()), 891 ModelSignKeyID: model.SignKeyID(), 892 } 893 894 r := setupUC20Bootenv( 895 c, 896 tab.MockBootloader, 897 &bootenv20Setup{ 898 modeenv: m, 899 kern: s.kern1, 900 kernStatus: boot.DefaultStatus, 901 }, 902 ) 903 defer r() 904 905 resealCalls := 0 906 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 907 resealCalls++ 908 909 c.Assert(params.ModelParams, HasLen, 1) 910 mp := params.ModelParams[0] 911 c.Check(mp.Model.Model(), Equals, model.Model()) 912 for _, ch := range mp.EFILoadChains { 913 printChain(c, ch, "-") 914 } 915 c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{ 916 secboot.NewLoadChain(assetBf, 917 secboot.NewLoadChain(runKernelBf)), 918 secboot.NewLoadChain(assetBf, 919 // TODO:UC20: once mock trusted assets 920 // bootloader can generated boot files for the 921 // kernel this will use candidate kernel 922 secboot.NewLoadChain(runKernelBf)), 923 }) 924 // actual paths are seen only here 925 c.Check(tab.BootChainKernelPath, DeepEquals, []string{ 926 s.kern1.MountFile(), 927 s.kern2.MountFile(), 928 }) 929 return nil 930 }) 931 defer restore() 932 933 // get the boot kernel participant from our new kernel snap 934 bootKern := boot.Participant(s.kern2, snap.TypeKernel, coreDev) 935 // make sure it's not a trivial boot participant 936 c.Assert(bootKern.IsTrivial(), Equals, false) 937 938 // make the kernel used on next boot 939 rebootRequired, err := bootKern.SetNextBoot() 940 c.Assert(err, IsNil) 941 c.Assert(rebootRequired, Equals, true) 942 943 // make sure the env was updated 944 bvars, err := tab.GetBootVars("kernel_status", "snap_kernel", "snap_try_kernel") 945 c.Assert(err, IsNil) 946 c.Assert(bvars, DeepEquals, map[string]string{ 947 "kernel_status": boot.TryStatus, 948 "snap_kernel": s.kern1.Filename(), 949 "snap_try_kernel": s.kern2.Filename(), 950 }) 951 952 // and that the modeenv now has this kernel listed 953 m2, err := boot.ReadModeenv("") 954 c.Assert(err, IsNil) 955 c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename(), s.kern2.Filename()}) 956 957 c.Check(resealCalls, Equals, 1) 958 } 959 960 func (s *bootenv20Suite) TestCoreParticipant20SetNextNewUnassertedKernelSnapWithReseal(c *C) { 961 // checked by resealKeyToModeenv 962 s.stampSealedKeys(c, dirs.GlobalRootDir) 963 964 tab := s.bootloaderWithTrustedAssets(c, []string{"asset"}) 965 966 data := []byte("foobar") 967 // SHA3-384 968 dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8" 969 970 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil) 971 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir), 0755), IsNil) 972 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil) 973 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil) 974 975 // mock the files in cache 976 mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{ 977 "asset-" + dataHash, 978 }) 979 980 assetBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)), bootloader.RoleRunMode) 981 runKernelBf := bootloader.NewBootFile(filepath.Join(s.ukern1.Filename()), "kernel.efi", bootloader.RoleRunMode) 982 983 tab.BootChainList = []bootloader.BootFile{ 984 bootloader.NewBootFile("", "asset", bootloader.RoleRunMode), 985 // TODO:UC20: fix mocked trusted assets bootloader to actually 986 // geenerate kernel boot files 987 runKernelBf, 988 } 989 990 uc20Model := boottest.MakeMockUC20Model() 991 coreDev := boottest.MockUC20Device("", uc20Model) 992 c.Assert(coreDev.HasModeenv(), Equals, true) 993 994 m := &boot.Modeenv{ 995 Mode: "run", 996 Base: s.base1.Filename(), 997 CurrentKernels: []string{s.ukern1.Filename()}, 998 CurrentTrustedBootAssets: boot.BootAssetsMap{ 999 "asset": {dataHash}, 1000 }, 1001 Model: uc20Model.Model(), 1002 BrandID: uc20Model.BrandID(), 1003 Grade: string(uc20Model.Grade()), 1004 ModelSignKeyID: uc20Model.SignKeyID(), 1005 } 1006 1007 r := setupUC20Bootenv( 1008 c, 1009 tab.MockBootloader, 1010 &bootenv20Setup{ 1011 modeenv: m, 1012 kern: s.ukern1, 1013 kernStatus: boot.DefaultStatus, 1014 }, 1015 ) 1016 defer r() 1017 1018 resealCalls := 0 1019 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 1020 resealCalls++ 1021 1022 c.Assert(params.ModelParams, HasLen, 1) 1023 mp := params.ModelParams[0] 1024 c.Check(mp.Model.Model(), Equals, uc20Model.Model()) 1025 for _, ch := range mp.EFILoadChains { 1026 printChain(c, ch, "-") 1027 } 1028 c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{ 1029 secboot.NewLoadChain(assetBf, 1030 secboot.NewLoadChain(runKernelBf)), 1031 secboot.NewLoadChain(assetBf, 1032 // TODO:UC20: once mock trusted assets 1033 // bootloader can generated boot files for the 1034 // kernel this will use candidate kernel 1035 secboot.NewLoadChain(runKernelBf)), 1036 }) 1037 // actual paths are seen only here 1038 c.Check(tab.BootChainKernelPath, DeepEquals, []string{ 1039 s.ukern1.MountFile(), 1040 s.ukern2.MountFile(), 1041 }) 1042 return nil 1043 }) 1044 defer restore() 1045 1046 // get the boot kernel participant from our new kernel snap 1047 bootKern := boot.Participant(s.ukern2, snap.TypeKernel, coreDev) 1048 // make sure it's not a trivial boot participant 1049 c.Assert(bootKern.IsTrivial(), Equals, false) 1050 1051 // make the kernel used on next boot 1052 rebootRequired, err := bootKern.SetNextBoot() 1053 c.Assert(err, IsNil) 1054 c.Assert(rebootRequired, Equals, true) 1055 1056 bvars, err := tab.GetBootVars("kernel_status", "snap_kernel", "snap_try_kernel") 1057 c.Assert(err, IsNil) 1058 c.Assert(bvars, DeepEquals, map[string]string{ 1059 "kernel_status": boot.TryStatus, 1060 "snap_kernel": s.ukern1.Filename(), 1061 "snap_try_kernel": s.ukern2.Filename(), 1062 }) 1063 1064 // and that the modeenv now has this kernel listed 1065 m2, err := boot.ReadModeenv("") 1066 c.Assert(err, IsNil) 1067 c.Assert(m2.CurrentKernels, DeepEquals, []string{s.ukern1.Filename(), s.ukern2.Filename()}) 1068 1069 c.Check(resealCalls, Equals, 1) 1070 } 1071 1072 func (s *bootenv20Suite) TestCoreParticipant20SetNextSameKernelSnapNoReseal(c *C) { 1073 // checked by resealKeyToModeenv 1074 s.stampSealedKeys(c, dirs.GlobalRootDir) 1075 1076 tab := s.bootloaderWithTrustedAssets(c, []string{"asset"}) 1077 1078 data := []byte("foobar") 1079 // SHA3-384 1080 dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8" 1081 1082 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil) 1083 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir), 0755), IsNil) 1084 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil) 1085 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil) 1086 1087 // mock the files in cache 1088 mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{ 1089 "asset-" + dataHash, 1090 }) 1091 1092 runKernelBf := bootloader.NewBootFile(filepath.Join(s.kern1.Filename()), "kernel.efi", bootloader.RoleRunMode) 1093 1094 tab.BootChainList = []bootloader.BootFile{ 1095 bootloader.NewBootFile("", "asset", bootloader.RoleRunMode), 1096 runKernelBf, 1097 } 1098 1099 uc20Model := boottest.MakeMockUC20Model() 1100 coreDev := boottest.MockUC20Device("", uc20Model) 1101 c.Assert(coreDev.HasModeenv(), Equals, true) 1102 1103 m := &boot.Modeenv{ 1104 Mode: "run", 1105 Base: s.base1.Filename(), 1106 CurrentKernels: []string{s.kern1.Filename()}, 1107 CurrentTrustedBootAssets: boot.BootAssetsMap{ 1108 "asset": {dataHash}, 1109 }, 1110 CurrentKernelCommandLines: boot.BootCommandLines{"snapd_recovery_mode=run"}, 1111 1112 Model: uc20Model.Model(), 1113 BrandID: uc20Model.BrandID(), 1114 Grade: string(uc20Model.Grade()), 1115 ModelSignKeyID: uc20Model.SignKeyID(), 1116 } 1117 1118 r := setupUC20Bootenv( 1119 c, 1120 tab.MockBootloader, 1121 &bootenv20Setup{ 1122 modeenv: m, 1123 kern: s.kern1, 1124 kernStatus: boot.DefaultStatus, 1125 }, 1126 ) 1127 defer r() 1128 1129 resealCalls := 0 1130 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 1131 resealCalls++ 1132 return fmt.Errorf("unexpected call") 1133 }) 1134 defer restore() 1135 1136 // get the boot kernel participant from our kernel snap 1137 bootKern := boot.Participant(s.kern1, snap.TypeKernel, coreDev) 1138 // make sure it's not a trivial boot participant 1139 c.Assert(bootKern.IsTrivial(), Equals, false) 1140 1141 // write boot-chains for current state that will stay unchanged 1142 bootChains := []boot.BootChain{{ 1143 BrandID: "my-brand", 1144 Model: "my-model-uc20", 1145 Grade: "dangerous", 1146 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 1147 AssetChain: []boot.BootAsset{ 1148 { 1149 Role: bootloader.RoleRunMode, 1150 Name: "asset", 1151 Hashes: []string{ 1152 "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8", 1153 }, 1154 }, 1155 }, 1156 Kernel: "pc-kernel", 1157 KernelRevision: "1", 1158 KernelCmdlines: []string{"snapd_recovery_mode=run"}, 1159 }} 1160 err := boot.WriteBootChains(bootChains, filepath.Join(dirs.SnapFDEDir, "boot-chains"), 0) 1161 c.Assert(err, IsNil) 1162 1163 // make the kernel used on next boot 1164 rebootRequired, err := bootKern.SetNextBoot() 1165 c.Assert(err, IsNil) 1166 c.Assert(rebootRequired, Equals, false) 1167 1168 // make sure the env is as expected 1169 bvars, err := tab.GetBootVars("kernel_status", "snap_kernel", "snap_try_kernel") 1170 c.Assert(err, IsNil) 1171 c.Assert(bvars, DeepEquals, map[string]string{ 1172 "kernel_status": boot.DefaultStatus, 1173 "snap_kernel": s.kern1.Filename(), 1174 "snap_try_kernel": "", 1175 }) 1176 1177 // and that the modeenv now has the one kernel listed 1178 m2, err := boot.ReadModeenv("") 1179 c.Assert(err, IsNil) 1180 c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename()}) 1181 1182 // boot chains were built 1183 c.Check(tab.BootChainKernelPath, DeepEquals, []string{ 1184 s.kern1.MountFile(), 1185 }) 1186 // no actual reseal 1187 c.Check(resealCalls, Equals, 0) 1188 } 1189 1190 func (s *bootenv20Suite) TestCoreParticipant20SetNextSameUnassertedKernelSnapNoReseal(c *C) { 1191 // checked by resealKeyToModeenv 1192 s.stampSealedKeys(c, dirs.GlobalRootDir) 1193 1194 tab := s.bootloaderWithTrustedAssets(c, []string{"asset"}) 1195 1196 data := []byte("foobar") 1197 // SHA3-384 1198 dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8" 1199 1200 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil) 1201 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir), 0755), IsNil) 1202 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil) 1203 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil) 1204 1205 // mock the files in cache 1206 mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{ 1207 "asset-" + dataHash, 1208 }) 1209 1210 runKernelBf := bootloader.NewBootFile(filepath.Join(s.ukern1.Filename()), "kernel.efi", bootloader.RoleRunMode) 1211 1212 tab.BootChainList = []bootloader.BootFile{ 1213 bootloader.NewBootFile("", "asset", bootloader.RoleRunMode), 1214 runKernelBf, 1215 } 1216 1217 uc20Model := boottest.MakeMockUC20Model() 1218 coreDev := boottest.MockUC20Device("", uc20Model) 1219 c.Assert(coreDev.HasModeenv(), Equals, true) 1220 1221 m := &boot.Modeenv{ 1222 Mode: "run", 1223 Base: s.base1.Filename(), 1224 CurrentKernels: []string{s.ukern1.Filename()}, 1225 CurrentTrustedBootAssets: boot.BootAssetsMap{ 1226 "asset": {dataHash}, 1227 }, 1228 CurrentKernelCommandLines: boot.BootCommandLines{"snapd_recovery_mode=run"}, 1229 1230 Model: uc20Model.Model(), 1231 BrandID: uc20Model.BrandID(), 1232 Grade: string(uc20Model.Grade()), 1233 ModelSignKeyID: uc20Model.SignKeyID(), 1234 } 1235 1236 r := setupUC20Bootenv( 1237 c, 1238 tab.MockBootloader, 1239 &bootenv20Setup{ 1240 modeenv: m, 1241 kern: s.ukern1, 1242 kernStatus: boot.DefaultStatus, 1243 }, 1244 ) 1245 defer r() 1246 1247 resealCalls := 0 1248 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 1249 resealCalls++ 1250 return fmt.Errorf("unexpected call") 1251 }) 1252 defer restore() 1253 1254 // get the boot kernel participant from our kernel snap 1255 bootKern := boot.Participant(s.ukern1, snap.TypeKernel, coreDev) 1256 // make sure it's not a trivial boot participant 1257 c.Assert(bootKern.IsTrivial(), Equals, false) 1258 1259 // write boot-chains for current state that will stay unchanged 1260 bootChains := []boot.BootChain{{ 1261 BrandID: "my-brand", 1262 Model: "my-model-uc20", 1263 Grade: "dangerous", 1264 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 1265 AssetChain: []boot.BootAsset{ 1266 { 1267 Role: bootloader.RoleRunMode, 1268 Name: "asset", 1269 Hashes: []string{ 1270 "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8", 1271 }, 1272 }, 1273 }, 1274 Kernel: "pc-kernel", 1275 KernelRevision: "", 1276 KernelCmdlines: []string{"snapd_recovery_mode=run"}, 1277 }} 1278 err := boot.WriteBootChains(bootChains, filepath.Join(dirs.SnapFDEDir, "boot-chains"), 0) 1279 c.Assert(err, IsNil) 1280 1281 // make the kernel used on next boot 1282 rebootRequired, err := bootKern.SetNextBoot() 1283 c.Assert(err, IsNil) 1284 c.Assert(rebootRequired, Equals, false) 1285 1286 // make sure the env is as expected 1287 bvars, err := tab.GetBootVars("kernel_status", "snap_kernel", "snap_try_kernel") 1288 c.Assert(err, IsNil) 1289 c.Assert(bvars, DeepEquals, map[string]string{ 1290 "kernel_status": boot.DefaultStatus, 1291 "snap_kernel": s.ukern1.Filename(), 1292 "snap_try_kernel": "", 1293 }) 1294 1295 // and that the modeenv now has the one kernel listed 1296 m2, err := boot.ReadModeenv("") 1297 c.Assert(err, IsNil) 1298 c.Assert(m2.CurrentKernels, DeepEquals, []string{s.ukern1.Filename()}) 1299 1300 // boot chains were built 1301 c.Check(tab.BootChainKernelPath, DeepEquals, []string{ 1302 s.ukern1.MountFile(), 1303 }) 1304 // no actual reseal 1305 c.Check(resealCalls, Equals, 0) 1306 } 1307 1308 func (s *bootenv20EnvRefKernelSuite) TestCoreParticipant20SetNextNewKernelSnap(c *C) { 1309 coreDev := boottest.MockUC20Device("", nil) 1310 c.Assert(coreDev.HasModeenv(), Equals, true) 1311 1312 r := setupUC20Bootenv( 1313 c, 1314 s.bootloader, 1315 s.normalDefaultState, 1316 ) 1317 defer r() 1318 1319 // get the boot kernel participant from our new kernel snap 1320 bootKern := boot.Participant(s.kern2, snap.TypeKernel, coreDev) 1321 // make sure it's not a trivial boot participant 1322 c.Assert(bootKern.IsTrivial(), Equals, false) 1323 1324 // make the kernel used on next boot 1325 rebootRequired, err := bootKern.SetNextBoot() 1326 c.Assert(err, IsNil) 1327 c.Assert(rebootRequired, Equals, true) 1328 1329 // make sure the env was updated 1330 m := s.bootloader.BootVars 1331 c.Assert(m, DeepEquals, map[string]string{ 1332 "kernel_status": boot.TryStatus, 1333 "snap_kernel": s.kern1.Filename(), 1334 "snap_try_kernel": s.kern2.Filename(), 1335 }) 1336 1337 // and that the modeenv now has this kernel listed 1338 m2, err := boot.ReadModeenv("") 1339 c.Assert(err, IsNil) 1340 c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename(), s.kern2.Filename()}) 1341 } 1342 1343 func (s *bootenv20Suite) TestMarkBootSuccessful20KernelStatusTryingNoKernelSnapCleansUp(c *C) { 1344 coreDev := boottest.MockUC20Device("", nil) 1345 c.Assert(coreDev.HasModeenv(), Equals, true) 1346 1347 // set all the same vars as if we were doing trying, except don't set a try 1348 // kernel 1349 r := setupUC20Bootenv( 1350 c, 1351 s.bootloader, 1352 &bootenv20Setup{ 1353 modeenv: &boot.Modeenv{ 1354 Mode: "run", 1355 Base: s.base1.Filename(), 1356 CurrentKernels: []string{s.kern1.Filename(), s.kern2.Filename()}, 1357 }, 1358 kern: s.kern1, 1359 // no try-kernel 1360 kernStatus: boot.TryingStatus, 1361 }, 1362 ) 1363 defer r() 1364 1365 // mark successful 1366 err := boot.MarkBootSuccessful(coreDev) 1367 c.Assert(err, IsNil) 1368 1369 // check that the bootloader variable was cleaned 1370 expected := map[string]string{"kernel_status": boot.DefaultStatus} 1371 c.Assert(s.bootloader.BootVars, DeepEquals, expected) 1372 1373 // check that MarkBootSuccessful didn't enable a kernel (since there was no 1374 // try kernel) 1375 _, nEnableCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableKernel") 1376 c.Assert(nEnableCalls, Equals, 0) 1377 1378 // we will always end up disabling a try-kernel though as cleanup 1379 _, nDisableTryCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel") 1380 c.Assert(nDisableTryCalls, Equals, 1) 1381 1382 // do it again, verify it's still okay 1383 err = boot.MarkBootSuccessful(coreDev) 1384 c.Assert(err, IsNil) 1385 c.Assert(s.bootloader.BootVars, DeepEquals, expected) 1386 1387 // no new enabled kernels 1388 _, nEnableCalls = s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableKernel") 1389 c.Assert(nEnableCalls, Equals, 0) 1390 1391 // again we will try to cleanup any leftover try-kernels 1392 _, nDisableTryCalls = s.bootloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel") 1393 c.Assert(nDisableTryCalls, Equals, 2) 1394 1395 // check that the modeenv re-wrote the CurrentKernels 1396 m2, err := boot.ReadModeenv("") 1397 c.Assert(err, IsNil) 1398 c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename()}) 1399 } 1400 1401 func (s *bootenv20EnvRefKernelSuite) TestMarkBootSuccessful20KernelStatusTryingNoKernelSnapCleansUp(c *C) { 1402 coreDev := boottest.MockUC20Device("", nil) 1403 c.Assert(coreDev.HasModeenv(), Equals, true) 1404 1405 // set all the same vars as if we were doing trying, except don't set a try 1406 // kernel 1407 r := setupUC20Bootenv( 1408 c, 1409 s.bootloader, 1410 &bootenv20Setup{ 1411 modeenv: &boot.Modeenv{ 1412 Mode: "run", 1413 Base: s.base1.Filename(), 1414 CurrentKernels: []string{s.kern1.Filename(), s.kern2.Filename()}, 1415 }, 1416 kern: s.kern1, 1417 // no try-kernel 1418 kernStatus: boot.TryingStatus, 1419 }, 1420 ) 1421 defer r() 1422 1423 // mark successful 1424 err := boot.MarkBootSuccessful(coreDev) 1425 c.Assert(err, IsNil) 1426 1427 // make sure the env was updated 1428 expected := map[string]string{ 1429 "kernel_status": boot.DefaultStatus, 1430 "snap_kernel": s.kern1.Filename(), 1431 "snap_try_kernel": "", 1432 } 1433 c.Assert(s.bootloader.BootVars, DeepEquals, expected) 1434 1435 // do it again, verify it's still okay 1436 err = boot.MarkBootSuccessful(coreDev) 1437 c.Assert(err, IsNil) 1438 1439 c.Assert(s.bootloader.BootVars, DeepEquals, expected) 1440 1441 // check that the modeenv re-wrote the CurrentKernels 1442 m2, err := boot.ReadModeenv("") 1443 c.Assert(err, IsNil) 1444 c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename()}) 1445 } 1446 1447 func (s *bootenv20Suite) TestMarkBootSuccessful20BaseStatusTryingNoTryBaseSnapCleansUp(c *C) { 1448 m := &boot.Modeenv{ 1449 Mode: "run", 1450 Base: s.base1.Filename(), 1451 // no TryBase set 1452 BaseStatus: boot.TryingStatus, 1453 } 1454 r := setupUC20Bootenv( 1455 c, 1456 s.bootloader, 1457 &bootenv20Setup{ 1458 modeenv: m, 1459 // no kernel setup necessary 1460 }, 1461 ) 1462 defer r() 1463 1464 coreDev := boottest.MockUC20Device("", nil) 1465 c.Assert(coreDev.HasModeenv(), Equals, true) 1466 1467 // mark successful 1468 err := boot.MarkBootSuccessful(coreDev) 1469 c.Assert(err, IsNil) 1470 1471 // check that the modeenv base_status was re-written to default 1472 m2, err := boot.ReadModeenv("") 1473 c.Assert(err, IsNil) 1474 c.Assert(m2.BaseStatus, Equals, boot.DefaultStatus) 1475 c.Assert(m2.Base, Equals, m.Base) 1476 c.Assert(m2.TryBase, Equals, m.TryBase) 1477 1478 // do it again, verify it's still okay 1479 err = boot.MarkBootSuccessful(coreDev) 1480 c.Assert(err, IsNil) 1481 1482 m3, err := boot.ReadModeenv("") 1483 c.Assert(err, IsNil) 1484 c.Assert(m3.BaseStatus, Equals, boot.DefaultStatus) 1485 c.Assert(m3.Base, Equals, m.Base) 1486 c.Assert(m3.TryBase, Equals, m.TryBase) 1487 } 1488 1489 func (s *bootenv20Suite) TestCoreParticipant20SetNextSameBaseSnap(c *C) { 1490 coreDev := boottest.MockUC20Device("", nil) 1491 c.Assert(coreDev.HasModeenv(), Equals, true) 1492 1493 m := &boot.Modeenv{ 1494 Mode: "run", 1495 Base: s.base1.Filename(), 1496 } 1497 r := setupUC20Bootenv( 1498 c, 1499 s.bootloader, 1500 &bootenv20Setup{ 1501 modeenv: m, 1502 // no kernel setup necessary 1503 }, 1504 ) 1505 defer r() 1506 1507 // get the boot base participant from our base snap 1508 bootBase := boot.Participant(s.base1, snap.TypeBase, coreDev) 1509 // make sure it's not a trivial boot participant 1510 c.Assert(bootBase.IsTrivial(), Equals, false) 1511 1512 // make the base used on next boot 1513 rebootRequired, err := bootBase.SetNextBoot() 1514 c.Assert(err, IsNil) 1515 1516 // we don't need to reboot because it's the same base snap 1517 c.Assert(rebootRequired, Equals, false) 1518 1519 // make sure the modeenv wasn't changed 1520 m2, err := boot.ReadModeenv("") 1521 c.Assert(err, IsNil) 1522 c.Assert(m2.Base, Equals, m.Base) 1523 c.Assert(m2.BaseStatus, Equals, m.BaseStatus) 1524 c.Assert(m2.TryBase, Equals, m.TryBase) 1525 } 1526 1527 func (s *bootenv20Suite) TestCoreParticipant20SetNextNewBaseSnap(c *C) { 1528 coreDev := boottest.MockUC20Device("", nil) 1529 c.Assert(coreDev.HasModeenv(), Equals, true) 1530 1531 // default state 1532 m := &boot.Modeenv{ 1533 Mode: "run", 1534 Base: s.base1.Filename(), 1535 } 1536 r := setupUC20Bootenv( 1537 c, 1538 s.bootloader, 1539 &bootenv20Setup{ 1540 modeenv: m, 1541 // no kernel setup necessary 1542 }, 1543 ) 1544 defer r() 1545 1546 // get the boot base participant from our new base snap 1547 bootBase := boot.Participant(s.base2, snap.TypeBase, coreDev) 1548 // make sure it's not a trivial boot participant 1549 c.Assert(bootBase.IsTrivial(), Equals, false) 1550 1551 // make the base used on next boot 1552 rebootRequired, err := bootBase.SetNextBoot() 1553 c.Assert(err, IsNil) 1554 c.Assert(rebootRequired, Equals, true) 1555 1556 // make sure the modeenv was updated 1557 m2, err := boot.ReadModeenv("") 1558 c.Assert(err, IsNil) 1559 c.Assert(m2.Base, Equals, m.Base) 1560 c.Assert(m2.BaseStatus, Equals, boot.TryStatus) 1561 c.Assert(m2.TryBase, Equals, s.base2.Filename()) 1562 } 1563 1564 func (s *bootenv20Suite) TestCoreParticipant20SetNextNewBaseSnapNoReseal(c *C) { 1565 // checked by resealKeyToModeenv 1566 s.stampSealedKeys(c, dirs.GlobalRootDir) 1567 1568 // set up all the bits required for an encrypted system 1569 tab := s.bootloaderWithTrustedAssets(c, []string{"asset"}) 1570 data := []byte("foobar") 1571 // SHA3-384 1572 dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8" 1573 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil) 1574 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil) 1575 // mock the files in cache 1576 mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{ 1577 "asset-" + dataHash, 1578 }) 1579 runKernelBf := bootloader.NewBootFile(filepath.Join(s.kern1.Filename()), "kernel.efi", bootloader.RoleRunMode) 1580 // write boot-chains for current state that will stay unchanged even 1581 // though base is changed 1582 bootChains := []boot.BootChain{{ 1583 BrandID: "my-brand", 1584 Model: "my-model-uc20", 1585 Grade: "dangerous", 1586 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 1587 AssetChain: []boot.BootAsset{{ 1588 Role: bootloader.RoleRunMode, Name: "asset", Hashes: []string{ 1589 "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8", 1590 }, 1591 }}, 1592 Kernel: "pc-kernel", 1593 KernelRevision: "1", 1594 KernelCmdlines: []string{"snapd_recovery_mode=run"}, 1595 }} 1596 1597 err := boot.WriteBootChains(boot.ToPredictableBootChains(bootChains), filepath.Join(dirs.SnapFDEDir, "boot-chains"), 0) 1598 c.Assert(err, IsNil) 1599 1600 coreDev := boottest.MockUC20Device("", nil) 1601 c.Assert(coreDev.HasModeenv(), Equals, true) 1602 model := coreDev.Model() 1603 1604 resealCalls := 0 1605 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 1606 resealCalls++ 1607 return nil 1608 }) 1609 defer restore() 1610 1611 tab.BootChainList = []bootloader.BootFile{ 1612 bootloader.NewBootFile("", "asset", bootloader.RoleRunMode), 1613 runKernelBf, 1614 } 1615 1616 // default state 1617 m := &boot.Modeenv{ 1618 Mode: "run", 1619 Base: s.base1.Filename(), 1620 CurrentKernels: []string{s.kern1.Filename()}, 1621 CurrentTrustedBootAssets: boot.BootAssetsMap{ 1622 "asset": {dataHash}, 1623 }, 1624 1625 Model: model.Model(), 1626 BrandID: model.BrandID(), 1627 Grade: string(model.Grade()), 1628 ModelSignKeyID: model.SignKeyID(), 1629 } 1630 r := setupUC20Bootenv( 1631 c, 1632 tab.MockBootloader, 1633 &bootenv20Setup{ 1634 modeenv: m, 1635 // no kernel setup necessary 1636 }, 1637 ) 1638 defer r() 1639 1640 // get the boot base participant from our new base snap 1641 bootBase := boot.Participant(s.base2, snap.TypeBase, coreDev) 1642 // make sure it's not a trivial boot participant 1643 c.Assert(bootBase.IsTrivial(), Equals, false) 1644 1645 // make the base used on next boot 1646 rebootRequired, err := bootBase.SetNextBoot() 1647 c.Assert(err, IsNil) 1648 c.Assert(rebootRequired, Equals, true) 1649 1650 // make sure the modeenv was updated 1651 m2, err := boot.ReadModeenv("") 1652 c.Assert(err, IsNil) 1653 c.Assert(m2.Base, Equals, m.Base) 1654 c.Assert(m2.BaseStatus, Equals, boot.TryStatus) 1655 c.Assert(m2.TryBase, Equals, s.base2.Filename()) 1656 1657 // no reseal 1658 c.Check(resealCalls, Equals, 0) 1659 } 1660 1661 func (s *bootenvSuite) TestMarkBootSuccessfulAllSnap(c *C) { 1662 coreDev := boottest.MockDevice("some-snap") 1663 1664 s.bootloader.BootVars["snap_mode"] = boot.TryingStatus 1665 s.bootloader.BootVars["snap_try_core"] = "os1" 1666 s.bootloader.BootVars["snap_try_kernel"] = "k1" 1667 err := boot.MarkBootSuccessful(coreDev) 1668 c.Assert(err, IsNil) 1669 1670 expected := map[string]string{ 1671 // cleared 1672 "snap_mode": boot.DefaultStatus, 1673 "snap_try_kernel": "", 1674 "snap_try_core": "", 1675 // updated 1676 "snap_kernel": "k1", 1677 "snap_core": "os1", 1678 } 1679 c.Assert(s.bootloader.BootVars, DeepEquals, expected) 1680 1681 // do it again, verify its still valid 1682 err = boot.MarkBootSuccessful(coreDev) 1683 c.Assert(err, IsNil) 1684 c.Assert(s.bootloader.BootVars, DeepEquals, expected) 1685 } 1686 1687 func (s *bootenv20Suite) TestMarkBootSuccessful20AllSnap(c *C) { 1688 coreDev := boottest.MockUC20Device("", nil) 1689 c.Assert(coreDev.HasModeenv(), Equals, true) 1690 1691 // bonus points: we were trying both a base snap and a kernel snap 1692 m := &boot.Modeenv{ 1693 Mode: "run", 1694 Base: s.base1.Filename(), 1695 TryBase: s.base2.Filename(), 1696 BaseStatus: boot.TryingStatus, 1697 CurrentKernels: []string{s.kern1.Filename(), s.kern2.Filename()}, 1698 } 1699 r := setupUC20Bootenv( 1700 c, 1701 s.bootloader, 1702 &bootenv20Setup{ 1703 modeenv: m, 1704 kern: s.kern1, 1705 tryKern: s.kern2, 1706 kernStatus: boot.TryingStatus, 1707 }, 1708 ) 1709 defer r() 1710 1711 err := boot.MarkBootSuccessful(coreDev) 1712 c.Assert(err, IsNil) 1713 1714 // check the bootloader variables 1715 expected := map[string]string{ 1716 // cleared 1717 "kernel_status": boot.DefaultStatus, 1718 } 1719 c.Assert(s.bootloader.BootVars, DeepEquals, expected) 1720 1721 // check that we called EnableKernel() on the try-kernel 1722 actual, _ := s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableKernel") 1723 c.Assert(actual, DeepEquals, []snap.PlaceInfo{s.kern2}) 1724 1725 // and that we disabled a try kernel 1726 _, nDisableTryCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel") 1727 c.Assert(nDisableTryCalls, Equals, 1) 1728 1729 // also check that the modeenv was updated 1730 m2, err := boot.ReadModeenv("") 1731 c.Assert(err, IsNil) 1732 c.Assert(m2.Base, Equals, s.base2.Filename()) 1733 c.Assert(m2.TryBase, Equals, "") 1734 c.Assert(m2.BaseStatus, Equals, boot.DefaultStatus) 1735 c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern2.Filename()}) 1736 1737 // do it again, verify its still valid 1738 err = boot.MarkBootSuccessful(coreDev) 1739 c.Assert(err, IsNil) 1740 c.Assert(s.bootloader.BootVars, DeepEquals, expected) 1741 1742 // no new enabled kernels 1743 actual, _ = s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableKernel") 1744 c.Assert(actual, DeepEquals, []snap.PlaceInfo{s.kern2}) 1745 // we always disable the try kernel as a cleanup operation, so there's one 1746 // more call here 1747 _, nDisableTryCalls = s.bootloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel") 1748 c.Assert(nDisableTryCalls, Equals, 2) 1749 } 1750 1751 func (s *bootenv20EnvRefKernelSuite) TestMarkBootSuccessful20AllSnap(c *C) { 1752 coreDev := boottest.MockUC20Device("", nil) 1753 c.Assert(coreDev.HasModeenv(), Equals, true) 1754 1755 // bonus points: we were trying both a base snap and a kernel snap 1756 m := &boot.Modeenv{ 1757 Mode: "run", 1758 Base: s.base1.Filename(), 1759 TryBase: s.base2.Filename(), 1760 BaseStatus: boot.TryingStatus, 1761 CurrentKernels: []string{s.kern1.Filename(), s.kern2.Filename()}, 1762 } 1763 r := setupUC20Bootenv( 1764 c, 1765 s.bootloader, 1766 &bootenv20Setup{ 1767 modeenv: m, 1768 kern: s.kern1, 1769 tryKern: s.kern2, 1770 kernStatus: boot.TryingStatus, 1771 }, 1772 ) 1773 defer r() 1774 1775 err := boot.MarkBootSuccessful(coreDev) 1776 c.Assert(err, IsNil) 1777 1778 // check the bootloader variables 1779 expected := map[string]string{ 1780 // cleared 1781 "kernel_status": boot.DefaultStatus, 1782 "snap_try_kernel": "", 1783 // enabled new kernel 1784 "snap_kernel": s.kern2.Filename(), 1785 } 1786 c.Assert(s.bootloader.BootVars, DeepEquals, expected) 1787 1788 // also check that the modeenv was updated 1789 m2, err := boot.ReadModeenv("") 1790 c.Assert(err, IsNil) 1791 c.Assert(m2.Base, Equals, s.base2.Filename()) 1792 c.Assert(m2.TryBase, Equals, "") 1793 c.Assert(m2.BaseStatus, Equals, boot.DefaultStatus) 1794 c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern2.Filename()}) 1795 1796 // do it again, verify its still valid 1797 err = boot.MarkBootSuccessful(coreDev) 1798 c.Assert(err, IsNil) 1799 c.Assert(s.bootloader.BootVars, DeepEquals, expected) 1800 } 1801 1802 func (s *bootenvSuite) TestMarkBootSuccessfulKernelUpdate(c *C) { 1803 coreDev := boottest.MockDevice("some-snap") 1804 1805 s.bootloader.BootVars["snap_mode"] = boot.TryingStatus 1806 s.bootloader.BootVars["snap_core"] = "os1" 1807 s.bootloader.BootVars["snap_kernel"] = "k1" 1808 s.bootloader.BootVars["snap_try_core"] = "" 1809 s.bootloader.BootVars["snap_try_kernel"] = "k2" 1810 err := boot.MarkBootSuccessful(coreDev) 1811 c.Assert(err, IsNil) 1812 c.Assert(s.bootloader.BootVars, DeepEquals, map[string]string{ 1813 // cleared 1814 "snap_mode": boot.DefaultStatus, 1815 "snap_try_kernel": "", 1816 "snap_try_core": "", 1817 // unchanged 1818 "snap_core": "os1", 1819 // updated 1820 "snap_kernel": "k2", 1821 }) 1822 } 1823 1824 func (s *bootenvSuite) TestMarkBootSuccessfulBaseUpdate(c *C) { 1825 coreDev := boottest.MockDevice("some-snap") 1826 1827 s.bootloader.BootVars["snap_mode"] = boot.TryingStatus 1828 s.bootloader.BootVars["snap_core"] = "os1" 1829 s.bootloader.BootVars["snap_kernel"] = "k1" 1830 s.bootloader.BootVars["snap_try_core"] = "os2" 1831 s.bootloader.BootVars["snap_try_kernel"] = "" 1832 err := boot.MarkBootSuccessful(coreDev) 1833 c.Assert(err, IsNil) 1834 c.Assert(s.bootloader.BootVars, DeepEquals, map[string]string{ 1835 // cleared 1836 "snap_mode": boot.DefaultStatus, 1837 "snap_try_core": "", 1838 // unchanged 1839 "snap_kernel": "k1", 1840 "snap_try_kernel": "", 1841 // updated 1842 "snap_core": "os2", 1843 }) 1844 } 1845 1846 func (s *bootenv20Suite) TestMarkBootSuccessful20KernelUpdate(c *C) { 1847 // trying a kernel snap 1848 m := &boot.Modeenv{ 1849 Mode: "run", 1850 Base: s.base1.Filename(), 1851 CurrentKernels: []string{s.kern1.Filename(), s.kern2.Filename()}, 1852 } 1853 r := setupUC20Bootenv( 1854 c, 1855 s.bootloader, 1856 &bootenv20Setup{ 1857 modeenv: m, 1858 kern: s.kern1, 1859 tryKern: s.kern2, 1860 kernStatus: boot.TryingStatus, 1861 }, 1862 ) 1863 defer r() 1864 1865 coreDev := boottest.MockUC20Device("", nil) 1866 c.Assert(coreDev.HasModeenv(), Equals, true) 1867 1868 // mark successful 1869 err := boot.MarkBootSuccessful(coreDev) 1870 c.Assert(err, IsNil) 1871 1872 // check the bootloader variables 1873 expected := map[string]string{"kernel_status": boot.DefaultStatus} 1874 c.Assert(s.bootloader.BootVars, DeepEquals, expected) 1875 1876 // check that MarkBootSuccessful enabled the try kernel 1877 actual, _ := s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableKernel") 1878 c.Assert(actual, DeepEquals, []snap.PlaceInfo{s.kern2}) 1879 1880 // and that we disabled a try kernel 1881 _, nDisableTryCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel") 1882 c.Assert(nDisableTryCalls, Equals, 1) 1883 1884 // check that the new kernel is the only one in modeenv 1885 m2, err := boot.ReadModeenv("") 1886 c.Assert(err, IsNil) 1887 c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern2.Filename()}) 1888 1889 // do it again, verify its still valid 1890 err = boot.MarkBootSuccessful(coreDev) 1891 c.Assert(err, IsNil) 1892 c.Assert(s.bootloader.BootVars, DeepEquals, expected) 1893 1894 // no new bootloader calls 1895 actual, _ = s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableKernel") 1896 c.Assert(actual, DeepEquals, []snap.PlaceInfo{s.kern2}) 1897 1898 // we did disable the kernel again because we always do this to cleanup in 1899 // case there were leftovers 1900 _, nDisableTryCalls = s.bootloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel") 1901 c.Assert(nDisableTryCalls, Equals, 2) 1902 } 1903 1904 func (s *bootenv20Suite) TestMarkBootSuccessful20KernelUpdateWithReseal(c *C) { 1905 // checked by resealKeyToModeenv 1906 s.stampSealedKeys(c, dirs.GlobalRootDir) 1907 1908 tab := s.bootloaderWithTrustedAssets(c, []string{"asset"}) 1909 1910 data := []byte("foobar") 1911 // SHA3-384 1912 dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8" 1913 1914 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil) 1915 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil) 1916 1917 // mock the files in cache 1918 mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{ 1919 "asset-" + dataHash, 1920 }) 1921 1922 assetBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)), bootloader.RoleRunMode) 1923 newRunKernelBf := bootloader.NewBootFile(filepath.Join(s.kern2.Filename()), "kernel.efi", bootloader.RoleRunMode) 1924 1925 tab.BootChainList = []bootloader.BootFile{ 1926 bootloader.NewBootFile("", "asset", bootloader.RoleRunMode), 1927 newRunKernelBf, 1928 } 1929 1930 coreDev := boottest.MockUC20Device("", nil) 1931 c.Assert(coreDev.HasModeenv(), Equals, true) 1932 model := coreDev.Model() 1933 1934 // trying a kernel snap 1935 m := &boot.Modeenv{ 1936 Mode: "run", 1937 Base: s.base1.Filename(), 1938 CurrentKernels: []string{s.kern1.Filename(), s.kern2.Filename()}, 1939 CurrentTrustedBootAssets: boot.BootAssetsMap{ 1940 "asset": {dataHash}, 1941 }, 1942 Model: model.Model(), 1943 BrandID: model.BrandID(), 1944 Grade: string(model.Grade()), 1945 ModelSignKeyID: model.SignKeyID(), 1946 } 1947 r := setupUC20Bootenv( 1948 c, 1949 tab.MockBootloader, 1950 &bootenv20Setup{ 1951 modeenv: m, 1952 kern: s.kern1, 1953 tryKern: s.kern2, 1954 kernStatus: boot.TryingStatus, 1955 }, 1956 ) 1957 defer r() 1958 1959 // write boot-chains that describe a state in which we have a new kernel 1960 // candidate (pc-kernel_2) 1961 bootChains := []boot.BootChain{{ 1962 BrandID: "my-brand", 1963 Model: "my-model-uc20", 1964 Grade: "dangerous", 1965 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 1966 AssetChain: []boot.BootAsset{{ 1967 Role: bootloader.RoleRunMode, Name: "asset", Hashes: []string{ 1968 "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8", 1969 }, 1970 }}, 1971 Kernel: "pc-kernel", 1972 KernelRevision: "1", 1973 KernelCmdlines: []string{"snapd_recovery_mode=run"}, 1974 }, { 1975 BrandID: "my-brand", 1976 Model: "my-model-uc20", 1977 Grade: "dangerous", 1978 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 1979 AssetChain: []boot.BootAsset{{ 1980 Role: bootloader.RoleRunMode, Name: "asset", Hashes: []string{ 1981 "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8", 1982 }, 1983 }}, 1984 Kernel: "pc-kernel", 1985 KernelRevision: "2", 1986 KernelCmdlines: []string{"snapd_recovery_mode=run"}, 1987 }} 1988 1989 err := boot.WriteBootChains(boot.ToPredictableBootChains(bootChains), filepath.Join(dirs.SnapFDEDir, "boot-chains"), 0) 1990 c.Assert(err, IsNil) 1991 1992 resealCalls := 0 1993 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 1994 resealCalls++ 1995 1996 c.Assert(params.ModelParams, HasLen, 1) 1997 mp := params.ModelParams[0] 1998 c.Check(mp.Model.Model(), Equals, model.Model()) 1999 for _, ch := range mp.EFILoadChains { 2000 printChain(c, ch, "-") 2001 } 2002 c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{ 2003 secboot.NewLoadChain(assetBf, 2004 secboot.NewLoadChain(newRunKernelBf)), 2005 }) 2006 return nil 2007 }) 2008 defer restore() 2009 2010 // mark successful 2011 err = boot.MarkBootSuccessful(coreDev) 2012 c.Assert(err, IsNil) 2013 2014 c.Check(resealCalls, Equals, 1) 2015 // check the bootloader variables 2016 expected := map[string]string{ 2017 "kernel_status": boot.DefaultStatus, 2018 "snap_kernel": s.kern2.Filename(), 2019 "snap_try_kernel": boot.DefaultStatus, 2020 } 2021 c.Assert(tab.BootVars, DeepEquals, expected) 2022 c.Check(tab.BootChainKernelPath, DeepEquals, []string{s.kern2.MountFile()}) 2023 2024 // check that the new kernel is the only one in modeenv 2025 m2, err := boot.ReadModeenv("") 2026 c.Assert(err, IsNil) 2027 c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern2.Filename()}) 2028 } 2029 2030 func (s *bootenv20EnvRefKernelSuite) TestMarkBootSuccessful20KernelUpdate(c *C) { 2031 // trying a kernel snap 2032 m := &boot.Modeenv{ 2033 Mode: "run", 2034 Base: s.base1.Filename(), 2035 CurrentKernels: []string{s.kern1.Filename(), s.kern2.Filename()}, 2036 } 2037 r := setupUC20Bootenv( 2038 c, 2039 s.bootloader, 2040 &bootenv20Setup{ 2041 modeenv: m, 2042 kern: s.kern1, 2043 tryKern: s.kern2, 2044 kernStatus: boot.TryingStatus, 2045 }, 2046 ) 2047 defer r() 2048 2049 coreDev := boottest.MockUC20Device("", nil) 2050 c.Assert(coreDev.HasModeenv(), Equals, true) 2051 2052 // mark successful 2053 err := boot.MarkBootSuccessful(coreDev) 2054 c.Assert(err, IsNil) 2055 2056 // check the bootloader variables 2057 expected := map[string]string{ 2058 "kernel_status": boot.DefaultStatus, 2059 "snap_kernel": s.kern2.Filename(), 2060 "snap_try_kernel": "", 2061 } 2062 c.Assert(s.bootloader.BootVars, DeepEquals, expected) 2063 2064 // check that the new kernel is the only one in modeenv 2065 m2, err := boot.ReadModeenv("") 2066 c.Assert(err, IsNil) 2067 c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern2.Filename()}) 2068 2069 // do it again, verify its still valid 2070 err = boot.MarkBootSuccessful(coreDev) 2071 c.Assert(err, IsNil) 2072 c.Assert(s.bootloader.BootVars, DeepEquals, expected) 2073 c.Assert(s.bootloader.BootVars, DeepEquals, expected) 2074 } 2075 2076 func (s *bootenv20Suite) TestMarkBootSuccessful20BaseUpdate(c *C) { 2077 // we were trying a base snap 2078 m := &boot.Modeenv{ 2079 Mode: "run", 2080 Base: s.base1.Filename(), 2081 TryBase: s.base2.Filename(), 2082 BaseStatus: boot.TryingStatus, 2083 CurrentKernels: []string{s.kern1.Filename()}, 2084 } 2085 r := setupUC20Bootenv( 2086 c, 2087 s.bootloader, 2088 &bootenv20Setup{ 2089 modeenv: m, 2090 kern: s.kern1, 2091 kernStatus: boot.DefaultStatus, 2092 }, 2093 ) 2094 defer r() 2095 2096 coreDev := boottest.MockUC20Device("", nil) 2097 c.Assert(coreDev.HasModeenv(), Equals, true) 2098 2099 // mark successful 2100 err := boot.MarkBootSuccessful(coreDev) 2101 c.Assert(err, IsNil) 2102 2103 // check the modeenv 2104 m2, err := boot.ReadModeenv("") 2105 c.Assert(err, IsNil) 2106 c.Assert(m2.Base, Equals, s.base2.Filename()) 2107 c.Assert(m2.TryBase, Equals, "") 2108 c.Assert(m2.BaseStatus, Equals, "") 2109 2110 // do it again, verify its still valid 2111 err = boot.MarkBootSuccessful(coreDev) 2112 c.Assert(err, IsNil) 2113 2114 // check the modeenv again 2115 m3, err := boot.ReadModeenv("") 2116 c.Assert(err, IsNil) 2117 c.Assert(m3.Base, Equals, s.base2.Filename()) 2118 c.Assert(m3.TryBase, Equals, "") 2119 c.Assert(m3.BaseStatus, Equals, "") 2120 } 2121 2122 func (s *bootenv20Suite) bootloaderWithTrustedAssets(c *C, trustedAssets []string) *bootloadertest.MockTrustedAssetsBootloader { 2123 // TODO:UC20: this should be an ExtractedRecoveryKernelImageBootloader 2124 // because that would reflect our main currently supported 2125 // trusted assets bootloader (grub) 2126 tab := bootloadertest.Mock("trusted", "").WithTrustedAssets() 2127 bootloader.Force(tab) 2128 tab.TrustedAssetsList = trustedAssets 2129 s.AddCleanup(func() { bootloader.Force(nil) }) 2130 return tab 2131 } 2132 2133 func (s *bootenv20Suite) TestMarkBootSuccessful20BootAssetsUpdateHappy(c *C) { 2134 // checked by resealKeyToModeenv 2135 s.stampSealedKeys(c, dirs.GlobalRootDir) 2136 2137 tab := s.bootloaderWithTrustedAssets(c, []string{"asset", "shim"}) 2138 2139 data := []byte("foobar") 2140 // SHA3-384 2141 dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8" 2142 shim := []byte("shim") 2143 shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b" 2144 2145 c.Assert(os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755), IsNil) 2146 c.Assert(os.MkdirAll(boot.InitramfsUbuntuSeedDir, 0755), IsNil) 2147 // only asset for ubuntu 2148 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil) 2149 // shim and asset for seed 2150 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil) 2151 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "shim"), shim, 0644), IsNil) 2152 2153 // mock the files in cache 2154 mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{ 2155 "shim-recoveryshimhash", 2156 "shim-" + shimHash, 2157 "asset-assethash", 2158 "asset-recoveryassethash", 2159 "asset-" + dataHash, 2160 }) 2161 2162 shimBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("shim-%s", shimHash)), bootloader.RoleRecovery) 2163 assetBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)), bootloader.RoleRecovery) 2164 runKernelBf := bootloader.NewBootFile(filepath.Join(s.kern1.Filename()), "kernel.efi", bootloader.RoleRunMode) 2165 recoveryKernelBf := bootloader.NewBootFile("pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 2166 2167 tab.BootChainList = []bootloader.BootFile{ 2168 bootloader.NewBootFile("", "shim", bootloader.RoleRecovery), 2169 bootloader.NewBootFile("", "asset", bootloader.RoleRecovery), 2170 runKernelBf, 2171 } 2172 tab.RecoveryBootChainList = []bootloader.BootFile{ 2173 bootloader.NewBootFile("", "shim", bootloader.RoleRecovery), 2174 bootloader.NewBootFile("", "asset", bootloader.RoleRecovery), 2175 recoveryKernelBf, 2176 } 2177 2178 uc20Model := boottest.MakeMockUC20Model() 2179 2180 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 2181 return uc20Model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil 2182 }) 2183 defer restore() 2184 2185 // we were trying an update of boot assets 2186 m := &boot.Modeenv{ 2187 Mode: "run", 2188 Base: s.base1.Filename(), 2189 CurrentKernels: []string{s.kern1.Filename()}, 2190 CurrentTrustedBootAssets: boot.BootAssetsMap{ 2191 "asset": {"assethash", dataHash}, 2192 }, 2193 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 2194 "asset": {"recoveryassethash", dataHash}, 2195 "shim": {"recoveryshimhash", shimHash}, 2196 }, 2197 CurrentRecoverySystems: []string{"system"}, 2198 2199 Model: uc20Model.Model(), 2200 BrandID: uc20Model.BrandID(), 2201 Grade: string(uc20Model.Grade()), 2202 ModelSignKeyID: uc20Model.SignKeyID(), 2203 } 2204 r := setupUC20Bootenv( 2205 c, 2206 tab.MockBootloader, 2207 &bootenv20Setup{ 2208 modeenv: m, 2209 kern: s.kern1, 2210 kernStatus: boot.DefaultStatus, 2211 }, 2212 ) 2213 defer r() 2214 2215 coreDev := boottest.MockUC20Device("", uc20Model) 2216 c.Assert(coreDev.HasModeenv(), Equals, true) 2217 2218 resealCalls := 0 2219 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 2220 resealCalls++ 2221 2222 c.Assert(params.ModelParams, HasLen, 1) 2223 mp := params.ModelParams[0] 2224 c.Check(mp.Model.Model(), Equals, uc20Model.Model()) 2225 for _, ch := range mp.EFILoadChains { 2226 printChain(c, ch, "-") 2227 } 2228 switch resealCalls { 2229 case 1: 2230 c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{ 2231 secboot.NewLoadChain(shimBf, 2232 secboot.NewLoadChain(assetBf, 2233 secboot.NewLoadChain(recoveryKernelBf))), 2234 secboot.NewLoadChain(shimBf, 2235 secboot.NewLoadChain(assetBf, 2236 secboot.NewLoadChain(runKernelBf))), 2237 }) 2238 case 2: 2239 c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{ 2240 secboot.NewLoadChain(shimBf, 2241 secboot.NewLoadChain(assetBf, 2242 secboot.NewLoadChain(recoveryKernelBf))), 2243 }) 2244 default: 2245 c.Errorf("unexpected additional call to secboot.ResealKey (call # %d)", resealCalls) 2246 } 2247 return nil 2248 }) 2249 defer restore() 2250 2251 // mark successful 2252 err := boot.MarkBootSuccessful(coreDev) 2253 c.Assert(err, IsNil) 2254 2255 // check the modeenv 2256 m2, err := boot.ReadModeenv("") 2257 c.Assert(err, IsNil) 2258 // update assets are in the list 2259 c.Check(m2.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{ 2260 "asset": {dataHash}, 2261 }) 2262 c.Check(m2.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{ 2263 "asset": {dataHash}, 2264 "shim": {shimHash}, 2265 }) 2266 // unused files were dropped from cache 2267 checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{ 2268 filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-"+dataHash), 2269 filepath.Join(dirs.SnapBootAssetsDir, "trusted", "shim-"+shimHash), 2270 }) 2271 c.Check(resealCalls, Equals, 2) 2272 } 2273 2274 func (s *bootenv20Suite) TestMarkBootSuccessful20BootAssetsStableStateHappy(c *C) { 2275 // checked by resealKeyToModeenv 2276 s.stampSealedKeys(c, dirs.GlobalRootDir) 2277 2278 tab := s.bootloaderWithTrustedAssets(c, []string{"nested/asset", "shim"}) 2279 2280 data := []byte("foobar") 2281 // SHA3-384 2282 dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8" 2283 shim := []byte("shim") 2284 shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b" 2285 2286 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "nested"), 0755), IsNil) 2287 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "nested"), 0755), IsNil) 2288 // only asset for ubuntu-boot 2289 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "nested/asset"), data, 0644), IsNil) 2290 // shim and asset for ubuntu-seed 2291 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "nested/asset"), data, 0644), IsNil) 2292 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "shim"), shim, 0644), IsNil) 2293 2294 // mock the files in cache 2295 mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{ 2296 "shim-" + shimHash, 2297 "asset-" + dataHash, 2298 }) 2299 2300 runKernelBf := bootloader.NewBootFile(filepath.Join(s.kern1.Filename()), "kernel.efi", bootloader.RoleRunMode) 2301 recoveryKernelBf := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 2302 2303 tab.BootChainList = []bootloader.BootFile{ 2304 bootloader.NewBootFile("", "shim", bootloader.RoleRecovery), 2305 bootloader.NewBootFile("", "asset", bootloader.RoleRecovery), 2306 runKernelBf, 2307 } 2308 tab.RecoveryBootChainList = []bootloader.BootFile{ 2309 bootloader.NewBootFile("", "shim", bootloader.RoleRecovery), 2310 bootloader.NewBootFile("", "asset", bootloader.RoleRecovery), 2311 recoveryKernelBf, 2312 } 2313 2314 uc20Model := boottest.MakeMockUC20Model() 2315 2316 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 2317 return uc20Model, []*seed.Snap{mockNamedKernelSeedSnap(c, snap.R(1), "pc-kernel-recovery"), mockGadgetSeedSnap(c, nil)}, nil 2318 }) 2319 defer restore() 2320 2321 // we were trying an update of boot assets 2322 m := &boot.Modeenv{ 2323 Mode: "run", 2324 Base: s.base1.Filename(), 2325 CurrentKernels: []string{s.kern1.Filename()}, 2326 CurrentTrustedBootAssets: boot.BootAssetsMap{ 2327 "asset": {dataHash}, 2328 }, 2329 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 2330 "asset": {dataHash}, 2331 "shim": {shimHash}, 2332 }, 2333 CurrentRecoverySystems: []string{"system"}, 2334 CurrentKernelCommandLines: boot.BootCommandLines{"snapd_recovery_mode=run"}, 2335 2336 Model: uc20Model.Model(), 2337 BrandID: uc20Model.BrandID(), 2338 Grade: string(uc20Model.Grade()), 2339 ModelSignKeyID: uc20Model.SignKeyID(), 2340 } 2341 r := setupUC20Bootenv( 2342 c, 2343 tab.MockBootloader, 2344 &bootenv20Setup{ 2345 modeenv: m, 2346 kern: s.kern1, 2347 kernStatus: boot.DefaultStatus, 2348 }, 2349 ) 2350 defer r() 2351 2352 coreDev := boottest.MockUC20Device("", uc20Model) 2353 c.Assert(coreDev.HasModeenv(), Equals, true) 2354 2355 resealCalls := 0 2356 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 2357 resealCalls++ 2358 return nil 2359 }) 2360 defer restore() 2361 2362 // write boot-chains for current state that will stay unchanged 2363 bootChains := []boot.BootChain{{ 2364 BrandID: "my-brand", 2365 Model: "my-model-uc20", 2366 Grade: "dangerous", 2367 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 2368 AssetChain: []boot.BootAsset{{ 2369 Role: bootloader.RoleRecovery, Name: "shim", 2370 Hashes: []string{ 2371 "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b", 2372 }, 2373 }, { 2374 Role: bootloader.RoleRecovery, Name: "asset", Hashes: []string{ 2375 "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8", 2376 }, 2377 }}, 2378 Kernel: "pc-kernel", 2379 KernelRevision: "1", 2380 KernelCmdlines: []string{"snapd_recovery_mode=run"}, 2381 }, { 2382 BrandID: "my-brand", 2383 Model: "my-model-uc20", 2384 Grade: "dangerous", 2385 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 2386 AssetChain: []boot.BootAsset{{ 2387 Role: bootloader.RoleRecovery, Name: "shim", 2388 Hashes: []string{ 2389 "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b", 2390 }, 2391 }, { 2392 Role: bootloader.RoleRecovery, Name: "asset", Hashes: []string{ 2393 "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8", 2394 }, 2395 }}, 2396 Kernel: "pc-kernel-recovery", 2397 KernelRevision: "1", 2398 KernelCmdlines: []string{"snapd_recovery_mode=recover snapd_recovery_system=system"}, 2399 }} 2400 2401 recoveryBootChains := []boot.BootChain{bootChains[1]} 2402 2403 err := boot.WriteBootChains(boot.ToPredictableBootChains(bootChains), filepath.Join(dirs.SnapFDEDir, "boot-chains"), 0) 2404 c.Assert(err, IsNil) 2405 2406 err = boot.WriteBootChains(boot.ToPredictableBootChains(recoveryBootChains), filepath.Join(dirs.SnapFDEDir, "recovery-boot-chains"), 0) 2407 c.Assert(err, IsNil) 2408 2409 // mark successful 2410 err = boot.MarkBootSuccessful(coreDev) 2411 c.Assert(err, IsNil) 2412 2413 // modeenv is unchanged 2414 m2, err := boot.ReadModeenv("") 2415 c.Assert(err, IsNil) 2416 c.Check(m2.CurrentTrustedBootAssets, DeepEquals, m.CurrentTrustedBootAssets) 2417 c.Check(m2.CurrentTrustedRecoveryBootAssets, DeepEquals, m.CurrentTrustedRecoveryBootAssets) 2418 // files are still in cache 2419 checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{ 2420 filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-"+dataHash), 2421 filepath.Join(dirs.SnapBootAssetsDir, "trusted", "shim-"+shimHash), 2422 }) 2423 2424 // boot chains were built 2425 c.Check(tab.BootChainKernelPath, DeepEquals, []string{ 2426 s.kern1.MountFile(), 2427 }) 2428 // no actual reseal 2429 c.Check(resealCalls, Equals, 0) 2430 } 2431 2432 func (s *bootenv20Suite) TestMarkBootSuccessful20BootUnassertedKernelAssetsStableStateHappy(c *C) { 2433 // checked by resealKeyToModeenv 2434 s.stampSealedKeys(c, dirs.GlobalRootDir) 2435 2436 tab := s.bootloaderWithTrustedAssets(c, []string{"nested/asset", "shim"}) 2437 2438 data := []byte("foobar") 2439 // SHA3-384 2440 dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8" 2441 shim := []byte("shim") 2442 shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b" 2443 2444 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "nested"), 0755), IsNil) 2445 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "nested"), 0755), IsNil) 2446 // only asset for ubuntu-boot 2447 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "nested/asset"), data, 0644), IsNil) 2448 // shim and asset for ubuntu-seed 2449 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "nested/asset"), data, 0644), IsNil) 2450 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "shim"), shim, 0644), IsNil) 2451 2452 // mock the files in cache 2453 mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{ 2454 "shim-" + shimHash, 2455 "asset-" + dataHash, 2456 }) 2457 2458 runKernelBf := bootloader.NewBootFile(filepath.Join(s.ukern1.Filename()), "kernel.efi", bootloader.RoleRunMode) 2459 recoveryKernelBf := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 2460 2461 tab.BootChainList = []bootloader.BootFile{ 2462 bootloader.NewBootFile("", "shim", bootloader.RoleRecovery), 2463 bootloader.NewBootFile("", "asset", bootloader.RoleRecovery), 2464 runKernelBf, 2465 } 2466 tab.RecoveryBootChainList = []bootloader.BootFile{ 2467 bootloader.NewBootFile("", "shim", bootloader.RoleRecovery), 2468 bootloader.NewBootFile("", "asset", bootloader.RoleRecovery), 2469 recoveryKernelBf, 2470 } 2471 2472 uc20Model := boottest.MakeMockUC20Model() 2473 2474 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 2475 return uc20Model, []*seed.Snap{mockNamedKernelSeedSnap(c, snap.R(1), "pc-kernel-recovery"), mockGadgetSeedSnap(c, nil)}, nil 2476 }) 2477 defer restore() 2478 2479 // we were trying an update of boot assets 2480 m := &boot.Modeenv{ 2481 Mode: "run", 2482 Base: s.base1.Filename(), 2483 CurrentKernels: []string{s.ukern1.Filename()}, 2484 CurrentTrustedBootAssets: boot.BootAssetsMap{ 2485 "asset": {dataHash}, 2486 }, 2487 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 2488 "asset": {dataHash}, 2489 "shim": {shimHash}, 2490 }, 2491 CurrentRecoverySystems: []string{"system"}, 2492 GoodRecoverySystems: []string{"system"}, 2493 CurrentKernelCommandLines: boot.BootCommandLines{"snapd_recovery_mode=run"}, 2494 // leave this comment to keep old gofmt happy 2495 Model: "my-model-uc20", 2496 BrandID: "my-brand", 2497 Grade: "dangerous", 2498 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 2499 } 2500 r := setupUC20Bootenv( 2501 c, 2502 tab.MockBootloader, 2503 &bootenv20Setup{ 2504 modeenv: m, 2505 kern: s.ukern1, 2506 kernStatus: boot.DefaultStatus, 2507 }, 2508 ) 2509 defer r() 2510 2511 coreDev := boottest.MockUC20Device("", uc20Model) 2512 c.Assert(coreDev.HasModeenv(), Equals, true) 2513 2514 resealCalls := 0 2515 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 2516 resealCalls++ 2517 return nil 2518 }) 2519 defer restore() 2520 2521 // write boot-chains for current state that will stay unchanged 2522 bootChains := []boot.BootChain{{ 2523 BrandID: "my-brand", 2524 Model: "my-model-uc20", 2525 Grade: "dangerous", 2526 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 2527 AssetChain: []boot.BootAsset{{ 2528 Role: bootloader.RoleRecovery, Name: "shim", 2529 Hashes: []string{ 2530 "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b", 2531 }, 2532 }, { 2533 Role: bootloader.RoleRecovery, Name: "asset", Hashes: []string{ 2534 "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8", 2535 }, 2536 }}, 2537 Kernel: "pc-kernel", 2538 // unasserted kernel snap 2539 KernelRevision: "", 2540 KernelCmdlines: []string{"snapd_recovery_mode=run"}, 2541 }, { 2542 BrandID: "my-brand", 2543 Model: "my-model-uc20", 2544 Grade: "dangerous", 2545 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 2546 AssetChain: []boot.BootAsset{{ 2547 Role: bootloader.RoleRecovery, Name: "shim", 2548 Hashes: []string{ 2549 "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b", 2550 }, 2551 }, { 2552 Role: bootloader.RoleRecovery, Name: "asset", Hashes: []string{ 2553 "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8", 2554 }, 2555 }}, 2556 Kernel: "pc-kernel-recovery", 2557 KernelRevision: "1", 2558 KernelCmdlines: []string{"snapd_recovery_mode=recover snapd_recovery_system=system"}, 2559 }} 2560 2561 recoveryBootChains := []boot.BootChain{bootChains[1]} 2562 2563 err := boot.WriteBootChains(boot.ToPredictableBootChains(bootChains), filepath.Join(dirs.SnapFDEDir, "boot-chains"), 0) 2564 c.Assert(err, IsNil) 2565 2566 err = boot.WriteBootChains(boot.ToPredictableBootChains(recoveryBootChains), filepath.Join(dirs.SnapFDEDir, "recovery-boot-chains"), 0) 2567 c.Assert(err, IsNil) 2568 2569 // mark successful 2570 err = boot.MarkBootSuccessful(coreDev) 2571 c.Assert(err, IsNil) 2572 2573 // modeenv is unchanged 2574 m2, err := boot.ReadModeenv("") 2575 c.Assert(err, IsNil) 2576 c.Check(m2.CurrentTrustedBootAssets, DeepEquals, m.CurrentTrustedBootAssets) 2577 c.Check(m2.CurrentTrustedRecoveryBootAssets, DeepEquals, m.CurrentTrustedRecoveryBootAssets) 2578 // files are still in cache 2579 checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{ 2580 filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-"+dataHash), 2581 filepath.Join(dirs.SnapBootAssetsDir, "trusted", "shim-"+shimHash), 2582 }) 2583 2584 // boot chains were built 2585 c.Check(tab.BootChainKernelPath, DeepEquals, []string{ 2586 s.ukern1.MountFile(), 2587 }) 2588 // no actual reseal 2589 c.Check(resealCalls, Equals, 0) 2590 } 2591 2592 func (s *bootenv20Suite) TestMarkBootSuccessful20BootAssetsUpdateUnexpectedAsset(c *C) { 2593 tab := s.bootloaderWithTrustedAssets(c, []string{"EFI/asset"}) 2594 2595 data := []byte("foobar") 2596 // SHA3-384 2597 dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8" 2598 2599 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "EFI"), 0755), IsNil) 2600 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI"), 0755), IsNil) 2601 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "EFI/asset"), data, 0644), IsNil) 2602 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/asset"), data, 0644), IsNil) 2603 // mock some state in the cache 2604 mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{ 2605 "asset-one", 2606 "asset-two", 2607 }) 2608 2609 coreDev := boottest.MockUC20Device("", nil) 2610 c.Assert(coreDev.HasModeenv(), Equals, true) 2611 model := coreDev.Model() 2612 2613 // we were trying an update of boot assets 2614 m := &boot.Modeenv{ 2615 Mode: "run", 2616 Base: s.base1.Filename(), 2617 CurrentKernels: []string{s.kern1.Filename()}, 2618 CurrentTrustedBootAssets: boot.BootAssetsMap{ 2619 // hash will not match 2620 "asset": {"one", "two"}, 2621 }, 2622 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 2623 "asset": {"one", "two"}, 2624 }, 2625 Model: model.Model(), 2626 BrandID: model.BrandID(), 2627 Grade: string(model.Grade()), 2628 ModelSignKeyID: model.SignKeyID(), 2629 } 2630 r := setupUC20Bootenv( 2631 c, 2632 tab.MockBootloader, 2633 &bootenv20Setup{ 2634 modeenv: m, 2635 kern: s.kern1, 2636 kernStatus: boot.DefaultStatus, 2637 }, 2638 ) 2639 defer r() 2640 2641 // mark successful 2642 err := boot.MarkBootSuccessful(coreDev) 2643 c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot mark boot successful: cannot mark successful boot assets: system booted with unexpected run mode bootloader asset "EFI/asset" hash %s`, dataHash)) 2644 2645 // check the modeenv 2646 m2, err := boot.ReadModeenv("") 2647 c.Assert(err, IsNil) 2648 // modeenv is unchaged 2649 c.Check(m2.CurrentTrustedBootAssets, DeepEquals, m.CurrentTrustedBootAssets) 2650 c.Check(m2.CurrentTrustedRecoveryBootAssets, DeepEquals, m.CurrentTrustedRecoveryBootAssets) 2651 // nothing was removed from cache 2652 checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{ 2653 filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-one"), 2654 filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-two"), 2655 }) 2656 } 2657 2658 func (s *bootenv20Suite) setupMarkBootSuccessful20CommandLine(c *C, model *asserts.Model, mode string, cmdlines boot.BootCommandLines) *boot.Modeenv { 2659 // mock some state in the cache 2660 mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{ 2661 "asset-one", 2662 }) 2663 // a pending kernel command line change 2664 m := &boot.Modeenv{ 2665 Mode: mode, 2666 Base: s.base1.Filename(), 2667 CurrentKernels: []string{s.kern1.Filename()}, 2668 CurrentTrustedBootAssets: boot.BootAssetsMap{ 2669 "asset": {"one"}, 2670 }, 2671 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 2672 "asset": {"one"}, 2673 }, 2674 CurrentKernelCommandLines: cmdlines, 2675 2676 Model: model.Model(), 2677 BrandID: model.BrandID(), 2678 Grade: string(model.Grade()), 2679 ModelSignKeyID: model.SignKeyID(), 2680 } 2681 return m 2682 } 2683 2684 func (s *bootenv20Suite) TestMarkBootSuccessful20CommandLineUpdatedHappy(c *C) { 2685 s.mockCmdline(c, "snapd_recovery_mode=run candidate panic=-1") 2686 tab := s.bootloaderWithTrustedAssets(c, []string{"asset"}) 2687 coreDev := boottest.MockUC20Device("", nil) 2688 c.Assert(coreDev.HasModeenv(), Equals, true) 2689 m := s.setupMarkBootSuccessful20CommandLine(c, coreDev.Model(), "run", boot.BootCommandLines{ 2690 "snapd_recovery_mode=run panic=-1", 2691 "snapd_recovery_mode=run candidate panic=-1", 2692 }) 2693 2694 r := setupUC20Bootenv( 2695 c, 2696 tab.MockBootloader, 2697 &bootenv20Setup{ 2698 modeenv: m, 2699 kern: s.kern1, 2700 kernStatus: boot.DefaultStatus, 2701 }, 2702 ) 2703 defer r() 2704 // mark successful 2705 err := boot.MarkBootSuccessful(coreDev) 2706 c.Assert(err, IsNil) 2707 2708 // check the modeenv 2709 m2, err := boot.ReadModeenv("") 2710 c.Assert(err, IsNil) 2711 // modeenv is unchaged 2712 c.Check(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 2713 "snapd_recovery_mode=run candidate panic=-1", 2714 }) 2715 } 2716 2717 func (s *bootenv20Suite) TestMarkBootSuccessful20CommandLineUpdatedOld(c *C) { 2718 s.mockCmdline(c, "snapd_recovery_mode=run panic=-1") 2719 tab := s.bootloaderWithTrustedAssets(c, []string{"asset"}) 2720 coreDev := boottest.MockUC20Device("", nil) 2721 c.Assert(coreDev.HasModeenv(), Equals, true) 2722 m := s.setupMarkBootSuccessful20CommandLine(c, coreDev.Model(), "run", boot.BootCommandLines{ 2723 "snapd_recovery_mode=run panic=-1", 2724 "snapd_recovery_mode=run candidate panic=-1", 2725 }) 2726 r := setupUC20Bootenv( 2727 c, 2728 tab.MockBootloader, 2729 &bootenv20Setup{ 2730 modeenv: m, 2731 kern: s.kern1, 2732 kernStatus: boot.DefaultStatus, 2733 }, 2734 ) 2735 defer r() 2736 2737 // mark successful 2738 err := boot.MarkBootSuccessful(coreDev) 2739 c.Assert(err, IsNil) 2740 2741 // check the modeenv 2742 m2, err := boot.ReadModeenv("") 2743 c.Assert(err, IsNil) 2744 // modeenv is unchaged 2745 c.Check(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 2746 "snapd_recovery_mode=run panic=-1", 2747 }) 2748 } 2749 2750 func (s *bootenv20Suite) TestMarkBootSuccessful20CommandLineUpdatedMismatch(c *C) { 2751 s.mockCmdline(c, "snapd_recovery_mode=run different") 2752 tab := s.bootloaderWithTrustedAssets(c, []string{"asset"}) 2753 coreDev := boottest.MockUC20Device("", nil) 2754 c.Assert(coreDev.HasModeenv(), Equals, true) 2755 m := s.setupMarkBootSuccessful20CommandLine(c, coreDev.Model(), "run", boot.BootCommandLines{ 2756 "snapd_recovery_mode=run", 2757 "snapd_recovery_mode=run candidate", 2758 }) 2759 r := setupUC20Bootenv( 2760 c, 2761 tab.MockBootloader, 2762 &bootenv20Setup{ 2763 modeenv: m, 2764 kern: s.kern1, 2765 kernStatus: boot.DefaultStatus, 2766 }, 2767 ) 2768 defer r() 2769 2770 // mark successful 2771 err := boot.MarkBootSuccessful(coreDev) 2772 c.Assert(err, ErrorMatches, `cannot mark boot successful: cannot mark successful boot command line: current command line content "snapd_recovery_mode=run different" not matching any expected entry`) 2773 } 2774 2775 func (s *bootenv20Suite) TestMarkBootSuccessful20CommandLineUpdatedFallbackOnBootSuccessful(c *C) { 2776 s.mockCmdline(c, "snapd_recovery_mode=run panic=-1") 2777 tab := s.bootloaderWithTrustedAssets(c, []string{"asset"}) 2778 tab.StaticCommandLine = "panic=-1" 2779 coreDev := boottest.MockUC20Device("", nil) 2780 c.Assert(coreDev.HasModeenv(), Equals, true) 2781 m := s.setupMarkBootSuccessful20CommandLine(c, coreDev.Model(), "run", nil) 2782 r := setupUC20Bootenv( 2783 c, 2784 tab.MockBootloader, 2785 &bootenv20Setup{ 2786 modeenv: m, 2787 kern: s.kern1, 2788 kernStatus: boot.DefaultStatus, 2789 }, 2790 ) 2791 defer r() 2792 2793 // mark successful 2794 err := boot.MarkBootSuccessful(coreDev) 2795 c.Assert(err, IsNil) 2796 2797 // check the modeenv 2798 m2, err := boot.ReadModeenv("") 2799 c.Assert(err, IsNil) 2800 // modeenv is unchaged 2801 c.Check(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 2802 "snapd_recovery_mode=run panic=-1", 2803 }) 2804 } 2805 2806 func (s *bootenv20Suite) TestMarkBootSuccessful20CommandLineUpdatedFallbackOnBootMismatch(c *C) { 2807 s.mockCmdline(c, "snapd_recovery_mode=run panic=-1 unexpected") 2808 tab := s.bootloaderWithTrustedAssets(c, []string{"asset"}) 2809 tab.StaticCommandLine = "panic=-1" 2810 coreDev := boottest.MockUC20Device("", nil) 2811 c.Assert(coreDev.HasModeenv(), Equals, true) 2812 m := s.setupMarkBootSuccessful20CommandLine(c, coreDev.Model(), "run", nil) 2813 r := setupUC20Bootenv( 2814 c, 2815 tab.MockBootloader, 2816 &bootenv20Setup{ 2817 modeenv: m, 2818 kern: s.kern1, 2819 kernStatus: boot.DefaultStatus, 2820 }, 2821 ) 2822 defer r() 2823 2824 // mark successful 2825 err := boot.MarkBootSuccessful(coreDev) 2826 c.Assert(err, ErrorMatches, `cannot mark boot successful: cannot mark successful boot command line: unexpected current command line: "snapd_recovery_mode=run panic=-1 unexpected"`) 2827 } 2828 2829 func (s *bootenv20Suite) TestMarkBootSuccessful20CommandLineNonRunMode(c *C) { 2830 // recover mode 2831 s.mockCmdline(c, "snapd_recovery_mode=recover snapd_recovery_system=1234 panic=-1") 2832 tab := s.bootloaderWithTrustedAssets(c, []string{"asset"}) 2833 tab.StaticCommandLine = "panic=-1" 2834 coreDev := boottest.MockUC20Device("", nil) 2835 c.Assert(coreDev.HasModeenv(), Equals, true) 2836 // current command line does not match any of the run mode command lines 2837 m := s.setupMarkBootSuccessful20CommandLine(c, coreDev.Model(), "recover", boot.BootCommandLines{ 2838 "snapd_recovery_mode=run panic=-1", 2839 "snapd_recovery_mode=run candidate panic=-1", 2840 }) 2841 r := setupUC20Bootenv( 2842 c, 2843 tab.MockBootloader, 2844 &bootenv20Setup{ 2845 modeenv: m, 2846 kern: s.kern1, 2847 kernStatus: boot.DefaultStatus, 2848 }, 2849 ) 2850 defer r() 2851 2852 // mark successful 2853 err := boot.MarkBootSuccessful(coreDev) 2854 c.Assert(err, IsNil) 2855 2856 // check the modeenv 2857 m2, err := boot.ReadModeenv("") 2858 c.Assert(err, IsNil) 2859 // modeenv is unchaged 2860 c.Check(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 2861 "snapd_recovery_mode=run panic=-1", 2862 "snapd_recovery_mode=run candidate panic=-1", 2863 }) 2864 } 2865 2866 func (s *bootenv20Suite) TestMarkBootSuccessful20CommandLineUpdatedNoFDEManagedBootloader(c *C) { 2867 s.mockCmdline(c, "snapd_recovery_mode=run candidate panic=-1") 2868 tab := s.bootloaderWithTrustedAssets(c, nil) 2869 coreDev := boottest.MockUC20Device("", nil) 2870 c.Assert(coreDev.HasModeenv(), Equals, true) 2871 m := s.setupMarkBootSuccessful20CommandLine(c, coreDev.Model(), "run", boot.BootCommandLines{ 2872 "snapd_recovery_mode=run panic=-1", 2873 "snapd_recovery_mode=run candidate panic=-1", 2874 }) 2875 // without encryption, the trusted assets are not tracked in the modeenv, 2876 // but we still may want to track command lines so that the gadget can 2877 // contribute to the system command line 2878 m.CurrentTrustedBootAssets = nil 2879 m.CurrentTrustedRecoveryBootAssets = nil 2880 2881 r := setupUC20Bootenv( 2882 c, 2883 tab.MockBootloader, 2884 &bootenv20Setup{ 2885 modeenv: m, 2886 kern: s.kern1, 2887 kernStatus: boot.DefaultStatus, 2888 }, 2889 ) 2890 defer r() 2891 2892 // mark successful 2893 err := boot.MarkBootSuccessful(coreDev) 2894 c.Assert(err, IsNil) 2895 2896 // check the modeenv 2897 m2, err := boot.ReadModeenv("") 2898 c.Assert(err, IsNil) 2899 // modeenv is unchaged 2900 c.Check(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 2901 "snapd_recovery_mode=run candidate panic=-1", 2902 }) 2903 } 2904 2905 func (s *bootenv20Suite) TestMarkBootSuccessful20CommandLineCompatNonTrustedBootloader(c *C) { 2906 s.mockCmdline(c, "snapd_recovery_mode=run candidate panic=-1") 2907 // bootloader has no trusted assets 2908 bl := bootloadertest.Mock("not-trusted", "") 2909 bootloader.Force(bl) 2910 s.AddCleanup(func() { bootloader.Force(nil) }) 2911 coreDev := boottest.MockUC20Device("", nil) 2912 c.Assert(coreDev.HasModeenv(), Equals, true) 2913 m := s.setupMarkBootSuccessful20CommandLine(c, coreDev.Model(), "run", nil) 2914 // no trusted assets 2915 m.CurrentTrustedBootAssets = nil 2916 m.CurrentTrustedRecoveryBootAssets = nil 2917 // no kernel command lines tracked 2918 m.CurrentKernelCommandLines = nil 2919 2920 r := setupUC20Bootenv( 2921 c, 2922 bl, 2923 &bootenv20Setup{ 2924 modeenv: m, 2925 kern: s.kern1, 2926 kernStatus: boot.DefaultStatus, 2927 }, 2928 ) 2929 defer r() 2930 2931 // mark successful 2932 err := boot.MarkBootSuccessful(coreDev) 2933 c.Assert(err, IsNil) 2934 2935 // check the modeenv 2936 m2, err := boot.ReadModeenv("") 2937 c.Assert(err, IsNil) 2938 // modeenv isn't changed 2939 c.Check(m2.CurrentKernelCommandLines, HasLen, 0) 2940 } 2941 2942 func (s *bootenv20Suite) TestMarkBootSuccessful20SystemsCompat(c *C) { 2943 b := bootloadertest.Mock("mock", s.bootdir) 2944 s.forceBootloader(b) 2945 2946 m := &boot.Modeenv{ 2947 Mode: "run", 2948 Base: s.base1.Filename(), 2949 CurrentKernels: []string{s.kern1.Filename()}, 2950 CurrentRecoverySystems: []string{"1234"}, 2951 } 2952 2953 r := setupUC20Bootenv( 2954 c, 2955 b, 2956 &bootenv20Setup{ 2957 modeenv: m, 2958 kern: s.kern1, 2959 kernStatus: boot.DefaultStatus, 2960 }, 2961 ) 2962 defer r() 2963 2964 coreDev := boottest.MockUC20Device("", nil) 2965 c.Assert(coreDev.HasModeenv(), Equals, true) 2966 // mark successful 2967 err := boot.MarkBootSuccessful(coreDev) 2968 c.Assert(err, IsNil) 2969 2970 // check the modeenv 2971 m2, err := boot.ReadModeenv("") 2972 c.Assert(err, IsNil) 2973 // the list of good recovery systems has not been modified 2974 c.Check(m2.GoodRecoverySystems, DeepEquals, []string{"1234"}) 2975 c.Check(m2.CurrentRecoverySystems, DeepEquals, []string{"1234"}) 2976 } 2977 2978 func (s *bootenv20Suite) TestMarkBootSuccessful20SystemsPopulated(c *C) { 2979 b := bootloadertest.Mock("mock", s.bootdir) 2980 s.forceBootloader(b) 2981 2982 m := &boot.Modeenv{ 2983 Mode: "run", 2984 Base: s.base1.Filename(), 2985 CurrentKernels: []string{s.kern1.Filename()}, 2986 CurrentRecoverySystems: []string{"1234", "9999"}, 2987 GoodRecoverySystems: []string{"1234"}, 2988 } 2989 2990 r := setupUC20Bootenv( 2991 c, 2992 b, 2993 &bootenv20Setup{ 2994 modeenv: m, 2995 kern: s.kern1, 2996 kernStatus: boot.DefaultStatus, 2997 }, 2998 ) 2999 defer r() 3000 3001 coreDev := boottest.MockUC20Device("", nil) 3002 c.Assert(coreDev.HasModeenv(), Equals, true) 3003 // mark successful 3004 err := boot.MarkBootSuccessful(coreDev) 3005 c.Assert(err, IsNil) 3006 3007 // check the modeenv 3008 m2, err := boot.ReadModeenv("") 3009 c.Assert(err, IsNil) 3010 // good recovery systems has been populated 3011 c.Check(m2.GoodRecoverySystems, DeepEquals, []string{"1234"}) 3012 c.Check(m2.CurrentRecoverySystems, DeepEquals, []string{"1234", "9999"}) 3013 } 3014 3015 func (s *bootenv20Suite) TestMarkBootSuccessful20ModelSignKeyIDPopulated(c *C) { 3016 b := bootloadertest.Mock("mock", s.bootdir) 3017 s.forceBootloader(b) 3018 3019 coreDev := boottest.MockUC20Device("", nil) 3020 c.Assert(coreDev.HasModeenv(), Equals, true) 3021 3022 m := &boot.Modeenv{ 3023 Mode: "run", 3024 Base: s.base1.Filename(), 3025 CurrentKernels: []string{s.kern1.Filename()}, 3026 Model: "my-model-uc20", 3027 BrandID: "my-brand", 3028 Grade: "dangerous", 3029 // sign key ID is unset 3030 } 3031 3032 r := setupUC20Bootenv( 3033 c, 3034 b, 3035 &bootenv20Setup{ 3036 modeenv: m, 3037 kern: s.kern1, 3038 kernStatus: boot.DefaultStatus, 3039 }, 3040 ) 3041 defer r() 3042 3043 // mark successful 3044 err := boot.MarkBootSuccessful(coreDev) 3045 c.Assert(err, IsNil) 3046 3047 // check the modeenv 3048 m2, err := boot.ReadModeenv("") 3049 c.Assert(err, IsNil) 3050 // model's sign key ID has been set 3051 c.Check(m2.ModelSignKeyID, Equals, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij") 3052 c.Check(m2.Model, Equals, "my-model-uc20") 3053 c.Check(m2.BrandID, Equals, "my-brand") 3054 c.Check(m2.Grade, Equals, "dangerous") 3055 } 3056 3057 type recoveryBootenv20Suite struct { 3058 baseBootenvSuite 3059 3060 bootloader *bootloadertest.MockBootloader 3061 3062 dev boot.Device 3063 } 3064 3065 var _ = Suite(&recoveryBootenv20Suite{}) 3066 3067 func (s *recoveryBootenv20Suite) SetUpTest(c *C) { 3068 s.baseBootenvSuite.SetUpTest(c) 3069 3070 s.bootloader = bootloadertest.Mock("mock", c.MkDir()) 3071 s.forceBootloader(s.bootloader) 3072 3073 s.dev = boottest.MockUC20Device("", nil) 3074 } 3075 3076 func (s *recoveryBootenv20Suite) TestSetRecoveryBootSystemAndModeHappy(c *C) { 3077 err := boot.SetRecoveryBootSystemAndMode(s.dev, "1234", "install") 3078 c.Assert(err, IsNil) 3079 c.Check(s.bootloader.BootVars, DeepEquals, map[string]string{ 3080 "snapd_recovery_system": "1234", 3081 "snapd_recovery_mode": "install", 3082 }) 3083 } 3084 3085 func (s *recoveryBootenv20Suite) TestSetRecoveryBootSystemAndModeSetErr(c *C) { 3086 s.bootloader.SetErr = errors.New("no can do") 3087 err := boot.SetRecoveryBootSystemAndMode(s.dev, "1234", "install") 3088 c.Assert(err, ErrorMatches, `no can do`) 3089 } 3090 3091 func (s *recoveryBootenv20Suite) TestSetRecoveryBootSystemAndModeNonUC20(c *C) { 3092 non20Dev := boottest.MockDevice("some-snap") 3093 err := boot.SetRecoveryBootSystemAndMode(non20Dev, "1234", "install") 3094 c.Assert(err, Equals, boot.ErrUnsupportedSystemMode) 3095 } 3096 3097 func (s *recoveryBootenv20Suite) TestSetRecoveryBootSystemAndModeErrClumsy(c *C) { 3098 err := boot.SetRecoveryBootSystemAndMode(s.dev, "", "install") 3099 c.Assert(err, ErrorMatches, "internal error: system label is unset") 3100 err = boot.SetRecoveryBootSystemAndMode(s.dev, "1234", "") 3101 c.Assert(err, ErrorMatches, "internal error: system mode is unset") 3102 } 3103 3104 func (s *recoveryBootenv20Suite) TestSetRecoveryBootSystemAndModeRealHappy(c *C) { 3105 bootloader.Force(nil) 3106 3107 mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu") 3108 err := os.MkdirAll(mockSeedGrubDir, 0755) 3109 c.Assert(err, IsNil) 3110 err = ioutil.WriteFile(filepath.Join(mockSeedGrubDir, "grub.cfg"), nil, 0644) 3111 c.Assert(err, IsNil) 3112 3113 err = boot.SetRecoveryBootSystemAndMode(s.dev, "1234", "install") 3114 c.Assert(err, IsNil) 3115 3116 bl, err := bootloader.Find(boot.InitramfsUbuntuSeedDir, &bootloader.Options{Role: bootloader.RoleRecovery}) 3117 c.Assert(err, IsNil) 3118 3119 blvars, err := bl.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") 3120 c.Assert(err, IsNil) 3121 c.Check(blvars, DeepEquals, map[string]string{ 3122 "snapd_recovery_system": "1234", 3123 "snapd_recovery_mode": "install", 3124 }) 3125 } 3126 3127 type bootConfigSuite struct { 3128 baseBootenvSuite 3129 3130 bootloader *bootloadertest.MockTrustedAssetsBootloader 3131 gadgetSnap string 3132 } 3133 3134 var _ = Suite(&bootConfigSuite{}) 3135 3136 func (s *bootConfigSuite) SetUpTest(c *C) { 3137 s.baseBootenvSuite.SetUpTest(c) 3138 3139 s.bootloader = bootloadertest.Mock("trusted", c.MkDir()).WithTrustedAssets() 3140 s.bootloader.StaticCommandLine = "this is mocked panic=-1" 3141 s.bootloader.CandidateStaticCommandLine = "mocked candidate panic=-1" 3142 s.forceBootloader(s.bootloader) 3143 3144 s.mockCmdline(c, "snapd_recovery_mode=run this is mocked panic=-1") 3145 s.gadgetSnap = snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, nil) 3146 } 3147 3148 func (s *bootConfigSuite) mockCmdline(c *C, cmdline string) { 3149 c.Assert(ioutil.WriteFile(s.cmdlineFile, []byte(cmdline), 0644), IsNil) 3150 } 3151 3152 func (s *bootConfigSuite) TestBootConfigUpdateHappyNoKeysNoReseal(c *C) { 3153 coreDev := boottest.MockUC20Device("", nil) 3154 c.Assert(coreDev.HasModeenv(), Equals, true) 3155 3156 m := &boot.Modeenv{ 3157 Mode: "run", 3158 CurrentKernelCommandLines: boot.BootCommandLines{ 3159 "snapd_recovery_mode=run this is mocked panic=-1", 3160 }, 3161 } 3162 c.Assert(m.WriteTo(""), IsNil) 3163 3164 resealCalls := 0 3165 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 3166 resealCalls++ 3167 return nil 3168 }) 3169 defer restore() 3170 3171 updated, err := boot.UpdateManagedBootConfigs(coreDev, s.gadgetSnap) 3172 c.Assert(err, IsNil) 3173 c.Check(updated, Equals, false) 3174 c.Check(s.bootloader.UpdateCalls, Equals, 1) 3175 c.Check(resealCalls, Equals, 0) 3176 } 3177 3178 func (s *bootConfigSuite) TestBootConfigUpdateHappyWithReseal(c *C) { 3179 s.stampSealedKeys(c, dirs.GlobalRootDir) 3180 3181 coreDev := boottest.MockUC20Device("", nil) 3182 c.Assert(coreDev.HasModeenv(), Equals, true) 3183 3184 runKernelBf := bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_600.snap", "kernel.efi", bootloader.RoleRunMode) 3185 recoveryKernelBf := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 3186 mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{ 3187 "asset-hash-1", 3188 }) 3189 3190 s.bootloader.TrustedAssetsList = []string{"asset"} 3191 s.bootloader.BootChainList = []bootloader.BootFile{ 3192 bootloader.NewBootFile("", "asset", bootloader.RoleRunMode), 3193 runKernelBf, 3194 } 3195 s.bootloader.RecoveryBootChainList = []bootloader.BootFile{ 3196 bootloader.NewBootFile("", "asset", bootloader.RoleRecovery), 3197 recoveryKernelBf, 3198 } 3199 m := &boot.Modeenv{ 3200 Mode: "run", 3201 CurrentKernels: []string{"pc-kernel_500.snap"}, 3202 CurrentKernelCommandLines: boot.BootCommandLines{ 3203 "snapd_recovery_mode=run this is mocked panic=-1", 3204 }, 3205 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 3206 "asset": []string{"hash-1"}, 3207 }, 3208 CurrentTrustedBootAssets: boot.BootAssetsMap{ 3209 "asset": []string{"hash-1"}, 3210 }, 3211 } 3212 c.Assert(m.WriteTo(""), IsNil) 3213 3214 resealCalls := 0 3215 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 3216 resealCalls++ 3217 c.Assert(params, NotNil) 3218 c.Assert(params.ModelParams, HasLen, 1) 3219 c.Check(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 3220 "snapd_recovery_mode=run mocked candidate panic=-1", 3221 "snapd_recovery_mode=run this is mocked panic=-1", 3222 }) 3223 return nil 3224 }) 3225 defer restore() 3226 3227 updated, err := boot.UpdateManagedBootConfigs(coreDev, s.gadgetSnap) 3228 c.Assert(err, IsNil) 3229 c.Check(updated, Equals, false) 3230 c.Check(s.bootloader.UpdateCalls, Equals, 1) 3231 c.Check(resealCalls, Equals, 1) 3232 3233 m2, err := boot.ReadModeenv("") 3234 c.Assert(err, IsNil) 3235 c.Assert(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 3236 "snapd_recovery_mode=run this is mocked panic=-1", 3237 "snapd_recovery_mode=run mocked candidate panic=-1", 3238 }) 3239 } 3240 3241 func (s *bootConfigSuite) TestBootConfigUpdateHappyNoChange(c *C) { 3242 s.stampSealedKeys(c, dirs.GlobalRootDir) 3243 3244 coreDev := boottest.MockUC20Device("", nil) 3245 c.Assert(coreDev.HasModeenv(), Equals, true) 3246 3247 s.bootloader.StaticCommandLine = "mocked unchanged panic=-1" 3248 s.bootloader.CandidateStaticCommandLine = "mocked unchanged panic=-1" 3249 s.mockCmdline(c, "snapd_recovery_mode=run mocked unchanged panic=-1") 3250 3251 m := &boot.Modeenv{ 3252 Mode: "run", 3253 CurrentKernelCommandLines: boot.BootCommandLines{ 3254 "snapd_recovery_mode=run mocked unchanged panic=-1", 3255 }, 3256 } 3257 c.Assert(m.WriteTo(""), IsNil) 3258 3259 resealCalls := 0 3260 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 3261 resealCalls++ 3262 return nil 3263 }) 3264 defer restore() 3265 3266 updated, err := boot.UpdateManagedBootConfigs(coreDev, s.gadgetSnap) 3267 c.Assert(err, IsNil) 3268 c.Check(updated, Equals, false) 3269 c.Check(s.bootloader.UpdateCalls, Equals, 1) 3270 c.Check(resealCalls, Equals, 0) 3271 3272 m2, err := boot.ReadModeenv("") 3273 c.Assert(err, IsNil) 3274 c.Assert(m2.CurrentKernelCommandLines, HasLen, 1) 3275 } 3276 3277 func (s *bootConfigSuite) TestBootConfigUpdateNonUC20DoesNothing(c *C) { 3278 nonUC20coreDev := boottest.MockDevice("pc-kernel") 3279 c.Assert(nonUC20coreDev.HasModeenv(), Equals, false) 3280 updated, err := boot.UpdateManagedBootConfigs(nonUC20coreDev, s.gadgetSnap) 3281 c.Assert(err, IsNil) 3282 c.Check(updated, Equals, false) 3283 c.Check(s.bootloader.UpdateCalls, Equals, 0) 3284 } 3285 3286 func (s *bootConfigSuite) TestBootConfigUpdateBadModeErr(c *C) { 3287 uc20Dev := boottest.MockUC20Device("recover", nil) 3288 c.Assert(uc20Dev.HasModeenv(), Equals, true) 3289 updated, err := boot.UpdateManagedBootConfigs(uc20Dev, s.gadgetSnap) 3290 c.Assert(err, ErrorMatches, "internal error: boot config can only be updated in run mode") 3291 c.Check(updated, Equals, false) 3292 c.Check(s.bootloader.UpdateCalls, Equals, 0) 3293 } 3294 3295 func (s *bootConfigSuite) TestBootConfigUpdateFailErr(c *C) { 3296 coreDev := boottest.MockUC20Device("", nil) 3297 c.Assert(coreDev.HasModeenv(), Equals, true) 3298 3299 m := &boot.Modeenv{ 3300 Mode: "run", 3301 CurrentKernelCommandLines: boot.BootCommandLines{ 3302 "snapd_recovery_mode=run this is mocked panic=-1", 3303 }, 3304 } 3305 c.Assert(m.WriteTo(""), IsNil) 3306 3307 s.bootloader.UpdateErr = errors.New("update fail") 3308 3309 updated, err := boot.UpdateManagedBootConfigs(coreDev, s.gadgetSnap) 3310 c.Assert(err, ErrorMatches, "update fail") 3311 c.Check(updated, Equals, false) 3312 c.Check(s.bootloader.UpdateCalls, Equals, 1) 3313 } 3314 3315 func (s *bootConfigSuite) TestBootConfigUpdateCmdlineMismatchErr(c *C) { 3316 coreDev := boottest.MockUC20Device("", nil) 3317 c.Assert(coreDev.HasModeenv(), Equals, true) 3318 3319 m := &boot.Modeenv{ 3320 Mode: "run", 3321 } 3322 c.Assert(m.WriteTo(""), IsNil) 3323 3324 s.mockCmdline(c, "snapd_recovery_mode=run unexpected cmdline") 3325 3326 updated, err := boot.UpdateManagedBootConfigs(coreDev, s.gadgetSnap) 3327 c.Assert(err, ErrorMatches, `internal error: current kernel command lines is unset`) 3328 c.Check(updated, Equals, false) 3329 c.Check(s.bootloader.UpdateCalls, Equals, 0) 3330 } 3331 3332 func (s *bootConfigSuite) TestBootConfigUpdateNotManagedErr(c *C) { 3333 coreDev := boottest.MockUC20Device("", nil) 3334 c.Assert(coreDev.HasModeenv(), Equals, true) 3335 3336 bl := bootloadertest.Mock("not-managed", c.MkDir()) 3337 bootloader.Force(bl) 3338 defer bootloader.Force(nil) 3339 3340 m := &boot.Modeenv{ 3341 Mode: "run", 3342 } 3343 c.Assert(m.WriteTo(""), IsNil) 3344 3345 updated, err := boot.UpdateManagedBootConfigs(coreDev, s.gadgetSnap) 3346 c.Assert(err, IsNil) 3347 c.Check(updated, Equals, false) 3348 c.Check(s.bootloader.UpdateCalls, Equals, 0) 3349 } 3350 3351 func (s *bootConfigSuite) TestBootConfigUpdateBootloaderFindErr(c *C) { 3352 coreDev := boottest.MockUC20Device("", nil) 3353 c.Assert(coreDev.HasModeenv(), Equals, true) 3354 3355 bootloader.ForceError(errors.New("mocked find error")) 3356 defer bootloader.ForceError(nil) 3357 3358 m := &boot.Modeenv{ 3359 Mode: "run", 3360 } 3361 c.Assert(m.WriteTo(""), IsNil) 3362 3363 updated, err := boot.UpdateManagedBootConfigs(coreDev, s.gadgetSnap) 3364 c.Assert(err, ErrorMatches, "internal error: cannot find trusted assets bootloader under .*: mocked find error") 3365 c.Check(updated, Equals, false) 3366 c.Check(s.bootloader.UpdateCalls, Equals, 0) 3367 } 3368 3369 func (s *bootConfigSuite) TestBootConfigUpdateWithGadgetAndReseal(c *C) { 3370 s.stampSealedKeys(c, dirs.GlobalRootDir) 3371 3372 gadgetSnap := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{ 3373 {"cmdline.extra", "foo bar baz"}, 3374 }) 3375 coreDev := boottest.MockUC20Device("", nil) 3376 c.Assert(coreDev.HasModeenv(), Equals, true) 3377 3378 runKernelBf := bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_600.snap", "kernel.efi", bootloader.RoleRunMode) 3379 recoveryKernelBf := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 3380 mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{ 3381 "asset-hash-1", 3382 }) 3383 3384 s.bootloader.TrustedAssetsList = []string{"asset"} 3385 s.bootloader.BootChainList = []bootloader.BootFile{ 3386 bootloader.NewBootFile("", "asset", bootloader.RoleRunMode), 3387 runKernelBf, 3388 } 3389 s.bootloader.RecoveryBootChainList = []bootloader.BootFile{ 3390 bootloader.NewBootFile("", "asset", bootloader.RoleRecovery), 3391 recoveryKernelBf, 3392 } 3393 m := &boot.Modeenv{ 3394 Mode: "run", 3395 CurrentKernels: []string{"pc-kernel_500.snap"}, 3396 CurrentKernelCommandLines: boot.BootCommandLines{ 3397 // the extra arguments would be included in the current 3398 // command line already 3399 "snapd_recovery_mode=run this is mocked panic=-1 foo bar baz", 3400 }, 3401 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 3402 "asset": []string{"hash-1"}, 3403 }, 3404 CurrentTrustedBootAssets: boot.BootAssetsMap{ 3405 "asset": []string{"hash-1"}, 3406 }, 3407 } 3408 c.Assert(m.WriteTo(""), IsNil) 3409 3410 resealCalls := 0 3411 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 3412 resealCalls++ 3413 c.Assert(params, NotNil) 3414 c.Assert(params.ModelParams, HasLen, 1) 3415 c.Check(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 3416 "snapd_recovery_mode=run mocked candidate panic=-1 foo bar baz", 3417 "snapd_recovery_mode=run this is mocked panic=-1 foo bar baz", 3418 }) 3419 return nil 3420 }) 3421 defer restore() 3422 3423 updated, err := boot.UpdateManagedBootConfigs(coreDev, gadgetSnap) 3424 c.Assert(err, IsNil) 3425 c.Check(updated, Equals, false) 3426 c.Check(s.bootloader.UpdateCalls, Equals, 1) 3427 c.Check(resealCalls, Equals, 1) 3428 3429 m2, err := boot.ReadModeenv("") 3430 c.Assert(err, IsNil) 3431 c.Assert(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 3432 "snapd_recovery_mode=run this is mocked panic=-1 foo bar baz", 3433 "snapd_recovery_mode=run mocked candidate panic=-1 foo bar baz", 3434 }) 3435 } 3436 3437 func (s *bootConfigSuite) TestBootConfigUpdateWithGadgetFullAndReseal(c *C) { 3438 s.stampSealedKeys(c, dirs.GlobalRootDir) 3439 3440 gadgetSnap := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{ 3441 {"cmdline.full", "foo bar baz"}, 3442 }) 3443 coreDev := boottest.MockUC20Device("", nil) 3444 c.Assert(coreDev.HasModeenv(), Equals, true) 3445 3446 // a minimal bootloader and modeenv setup that works because reseal is 3447 // not executed 3448 s.bootloader.TrustedAssetsList = []string{"asset"} 3449 m := &boot.Modeenv{ 3450 Mode: "run", 3451 CurrentKernelCommandLines: boot.BootCommandLines{ 3452 // the full arguments would be included in the current 3453 // command line already 3454 "snapd_recovery_mode=run foo bar baz", 3455 }, 3456 } 3457 c.Assert(m.WriteTo(""), IsNil) 3458 3459 s.bootloader.Updated = true 3460 3461 resealCalls := 0 3462 // reseal does not happen, because the gadget overrides the static 3463 // command line which is part of boot config, thus there's no resulting 3464 // change in the command lines tracked in modeenv and no need to reseal 3465 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 3466 resealCalls++ 3467 return fmt.Errorf("unexpected call") 3468 }) 3469 defer restore() 3470 3471 updated, err := boot.UpdateManagedBootConfigs(coreDev, gadgetSnap) 3472 c.Assert(err, IsNil) 3473 c.Check(updated, Equals, true) 3474 c.Check(s.bootloader.UpdateCalls, Equals, 1) 3475 c.Check(resealCalls, Equals, 0) 3476 3477 m2, err := boot.ReadModeenv("") 3478 c.Assert(err, IsNil) 3479 c.Assert(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 3480 "snapd_recovery_mode=run foo bar baz", 3481 }) 3482 } 3483 3484 type bootKernelCommandLineSuite struct { 3485 baseBootenvSuite 3486 3487 bootloader *bootloadertest.MockTrustedAssetsBootloader 3488 gadgetSnap string 3489 uc20dev boot.Device 3490 recoveryKernelBf bootloader.BootFile 3491 runKernelBf bootloader.BootFile 3492 modeenvWithEncryption *boot.Modeenv 3493 resealCalls int 3494 resealCommandLines [][]string 3495 } 3496 3497 var _ = Suite(&bootKernelCommandLineSuite{}) 3498 3499 func (s *bootKernelCommandLineSuite) SetUpTest(c *C) { 3500 s.baseBootenvSuite.SetUpTest(c) 3501 3502 data := []byte("foobar") 3503 // SHA3-384 3504 dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8" 3505 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil) 3506 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir), 0755), IsNil) 3507 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil) 3508 c.Assert(ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil) 3509 3510 s.bootloader = bootloadertest.Mock("trusted", c.MkDir()).WithTrustedAssets() 3511 s.bootloader.TrustedAssetsList = []string{"asset"} 3512 s.bootloader.StaticCommandLine = "static mocked panic=-1" 3513 s.bootloader.CandidateStaticCommandLine = "mocked candidate panic=-1" 3514 s.forceBootloader(s.bootloader) 3515 3516 s.mockCmdline(c, "snapd_recovery_mode=run this is mocked panic=-1") 3517 s.gadgetSnap = snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, nil) 3518 s.uc20dev = boottest.MockUC20Device("", boottest.MakeMockUC20Model(nil)) 3519 s.runKernelBf = bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_600.snap", "kernel.efi", bootloader.RoleRunMode) 3520 s.recoveryKernelBf = bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 3521 mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{ 3522 "asset-" + dataHash, 3523 }) 3524 3525 s.bootloader.BootChainList = []bootloader.BootFile{ 3526 bootloader.NewBootFile("", "asset", bootloader.RoleRunMode), 3527 s.runKernelBf, 3528 } 3529 s.bootloader.RecoveryBootChainList = []bootloader.BootFile{ 3530 bootloader.NewBootFile("", "asset", bootloader.RoleRecovery), 3531 s.recoveryKernelBf, 3532 } 3533 s.modeenvWithEncryption = &boot.Modeenv{ 3534 Mode: "run", 3535 CurrentKernels: []string{"pc-kernel_500.snap"}, 3536 Base: "core20_1.snap", 3537 BaseStatus: boot.DefaultStatus, 3538 CurrentKernelCommandLines: boot.BootCommandLines{ 3539 // the extra arguments would be included in the current 3540 // command line already 3541 "snapd_recovery_mode=run static mocked panic=-1", 3542 }, 3543 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 3544 "asset": []string{dataHash}, 3545 }, 3546 CurrentTrustedBootAssets: boot.BootAssetsMap{ 3547 "asset": []string{dataHash}, 3548 }, 3549 } 3550 s.bootloader.SetBootVars(map[string]string{ 3551 "snap_kernel": "pc-kernel_500.snap", 3552 }) 3553 s.bootloader.SetBootVarsCalls = 0 3554 3555 s.resealCommandLines = nil 3556 s.resealCalls = 0 3557 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 3558 s.resealCalls++ 3559 c.Assert(params, NotNil) 3560 c.Assert(params.ModelParams, HasLen, 1) 3561 s.resealCommandLines = append(s.resealCommandLines, params.ModelParams[0].KernelCmdlines) 3562 return nil 3563 }) 3564 s.AddCleanup(restore) 3565 } 3566 3567 func (s *bootKernelCommandLineSuite) TestCommandLineUpdateNonUC20(c *C) { 3568 nonUC20dev := boottest.MockDevice("") 3569 3570 // gadget which would otherwise trigger an update 3571 sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{ 3572 {"cmdline.extra", "foo"}, 3573 }) 3574 3575 reboot, err := boot.UpdateCommandLineForGadgetComponent(nonUC20dev, sf) 3576 c.Assert(err, ErrorMatches, "internal error: command line component cannot be updated on non UC20 devices") 3577 c.Assert(reboot, Equals, false) 3578 } 3579 3580 func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20NotManagedBootloader(c *C) { 3581 // gadget which would otherwise trigger an update 3582 sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{ 3583 {"cmdline.extra", "foo"}, 3584 }) 3585 3586 // but the bootloader is not managed by snapd 3587 bl := bootloadertest.Mock("not-managed", c.MkDir()) 3588 bl.SetErr = fmt.Errorf("unexpected call") 3589 s.forceBootloader(bl) 3590 3591 reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf) 3592 c.Assert(err, IsNil) 3593 c.Assert(reboot, Equals, false) 3594 c.Check(bl.SetBootVarsCalls, Equals, 0) 3595 } 3596 3597 func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20ArgsAdded(c *C) { 3598 s.stampSealedKeys(c, dirs.GlobalRootDir) 3599 3600 sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{ 3601 {"cmdline.extra", "args from gadget"}, 3602 }) 3603 3604 s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run static mocked panic=-1"} 3605 c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil) 3606 3607 reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf) 3608 c.Assert(err, IsNil) 3609 c.Assert(reboot, Equals, true) 3610 3611 // reseal happened 3612 c.Check(s.resealCalls, Equals, 1) 3613 c.Check(s.resealCommandLines, DeepEquals, [][]string{{ 3614 "snapd_recovery_mode=run static mocked panic=-1", 3615 "snapd_recovery_mode=run static mocked panic=-1 args from gadget", 3616 }}) 3617 3618 // modeenv has been updated 3619 newM, err := boot.ReadModeenv("") 3620 c.Assert(err, IsNil) 3621 c.Check(newM.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 3622 "snapd_recovery_mode=run static mocked panic=-1", 3623 "snapd_recovery_mode=run static mocked panic=-1 args from gadget", 3624 }) 3625 3626 // bootloader variables too 3627 c.Check(s.bootloader.SetBootVarsCalls, Equals, 1) 3628 args, err := s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args") 3629 c.Assert(err, IsNil) 3630 c.Check(args, DeepEquals, map[string]string{ 3631 "snapd_extra_cmdline_args": "args from gadget", 3632 "snapd_full_cmdline_args": "", 3633 }) 3634 } 3635 3636 func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20ArgsSwitch(c *C) { 3637 s.stampSealedKeys(c, dirs.GlobalRootDir) 3638 3639 sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{ 3640 {"cmdline.extra", "no change"}, 3641 }) 3642 3643 s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run static mocked panic=-1 no change"} 3644 c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil) 3645 err := s.bootloader.SetBootVars(map[string]string{ 3646 "snapd_extra_cmdline_args": "no change", 3647 // this is intentionally filled and will be cleared 3648 "snapd_full_cmdline_args": "canary", 3649 }) 3650 c.Assert(err, IsNil) 3651 s.bootloader.SetBootVarsCalls = 0 3652 3653 reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf) 3654 c.Assert(err, IsNil) 3655 c.Assert(reboot, Equals, false) 3656 3657 // no reseal needed 3658 c.Check(s.resealCalls, Equals, 0) 3659 3660 newM, err := boot.ReadModeenv("") 3661 c.Assert(err, IsNil) 3662 c.Check(newM.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 3663 "snapd_recovery_mode=run static mocked panic=-1 no change", 3664 }) 3665 c.Check(s.bootloader.SetBootVarsCalls, Equals, 0) 3666 args, err := s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args") 3667 c.Assert(err, IsNil) 3668 c.Check(args, DeepEquals, map[string]string{ 3669 "snapd_extra_cmdline_args": "no change", 3670 // canary is still present, as nothing was modified 3671 "snapd_full_cmdline_args": "canary", 3672 }) 3673 3674 // let's change them now 3675 sfChanged := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{ 3676 {"cmdline.extra", "changed"}, 3677 }) 3678 3679 reboot, err = boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sfChanged) 3680 c.Assert(err, IsNil) 3681 c.Assert(reboot, Equals, true) 3682 3683 // reseal was applied 3684 c.Check(s.resealCalls, Equals, 1) 3685 c.Check(s.resealCommandLines, DeepEquals, [][]string{{ 3686 // those come from boot chains which use predictable sorting 3687 "snapd_recovery_mode=run static mocked panic=-1 changed", 3688 "snapd_recovery_mode=run static mocked panic=-1 no change", 3689 }}) 3690 3691 // modeenv has been updated 3692 newM, err = boot.ReadModeenv("") 3693 c.Assert(err, IsNil) 3694 c.Check(newM.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 3695 "snapd_recovery_mode=run static mocked panic=-1 no change", 3696 // new ones are appended 3697 "snapd_recovery_mode=run static mocked panic=-1 changed", 3698 }) 3699 // and bootloader env too 3700 args, err = s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args") 3701 c.Assert(err, IsNil) 3702 c.Check(args, DeepEquals, map[string]string{ 3703 "snapd_extra_cmdline_args": "changed", 3704 // canary has been cleared as bootenv was modified 3705 "snapd_full_cmdline_args": "", 3706 }) 3707 } 3708 3709 func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20UnencryptedArgsRemoved(c *C) { 3710 s.stampSealedKeys(c, dirs.GlobalRootDir) 3711 3712 // pretend we used to have additional arguments from the gadget, but 3713 // those will be gone with new update 3714 sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, nil) 3715 3716 s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run static mocked panic=-1 from-gadget"} 3717 c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil) 3718 err := s.bootloader.SetBootVars(map[string]string{ 3719 "snapd_extra_cmdline_args": "from-gadget", 3720 // this is intentionally filled and will be cleared 3721 "snapd_full_cmdline_args": "canary", 3722 }) 3723 c.Assert(err, IsNil) 3724 s.bootloader.SetBootVarsCalls = 0 3725 3726 reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf) 3727 c.Assert(err, IsNil) 3728 c.Assert(reboot, Equals, true) 3729 3730 c.Check(s.resealCalls, Equals, 1) 3731 c.Check(s.resealCommandLines, DeepEquals, [][]string{{ 3732 "snapd_recovery_mode=run static mocked panic=-1", 3733 "snapd_recovery_mode=run static mocked panic=-1 from-gadget", 3734 }}) 3735 3736 newM, err := boot.ReadModeenv("") 3737 c.Assert(err, IsNil) 3738 c.Check(newM.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 3739 "snapd_recovery_mode=run static mocked panic=-1 from-gadget", 3740 "snapd_recovery_mode=run static mocked panic=-1", 3741 }) 3742 // bootloader variables were explicitly cleared 3743 c.Check(s.bootloader.SetBootVarsCalls, Equals, 1) 3744 args, err := s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args") 3745 c.Assert(err, IsNil) 3746 c.Check(args, DeepEquals, map[string]string{ 3747 "snapd_extra_cmdline_args": "", 3748 "snapd_full_cmdline_args": "", 3749 }) 3750 } 3751 3752 func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20SetError(c *C) { 3753 s.stampSealedKeys(c, dirs.GlobalRootDir) 3754 3755 // pretend we used to have additional arguments from the gadget, but 3756 // those will be gone with new update 3757 sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{ 3758 {"cmdline.extra", "this-is-not-applied"}, 3759 }) 3760 3761 s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run static mocked panic=-1"} 3762 c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil) 3763 3764 s.bootloader.SetErr = fmt.Errorf("set fails") 3765 3766 reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf) 3767 c.Assert(err, ErrorMatches, "cannot set run system kernel command line arguments: set fails") 3768 c.Assert(reboot, Equals, false) 3769 // set boot vars was called and failed 3770 c.Check(s.bootloader.SetBootVarsCalls, Equals, 1) 3771 3772 // reseal with new parameters happened though 3773 c.Check(s.resealCalls, Equals, 1) 3774 c.Check(s.resealCommandLines, DeepEquals, [][]string{{ 3775 "snapd_recovery_mode=run static mocked panic=-1", 3776 "snapd_recovery_mode=run static mocked panic=-1 this-is-not-applied", 3777 }}) 3778 3779 newM, err := boot.ReadModeenv("") 3780 c.Assert(err, IsNil) 3781 c.Check(newM.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 3782 "snapd_recovery_mode=run static mocked panic=-1", 3783 // this will be cleared on next reboot or will get overwritten 3784 // by an update 3785 "snapd_recovery_mode=run static mocked panic=-1 this-is-not-applied", 3786 }) 3787 } 3788 3789 func (s *bootKernelCommandLineSuite) TestCommandLineUpdateWithResealError(c *C) { 3790 gadgetSnap := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{ 3791 {"cmdline.extra", "args from gadget"}, 3792 }) 3793 3794 s.stampSealedKeys(c, dirs.GlobalRootDir) 3795 c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil) 3796 3797 resealCalls := 0 3798 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 3799 resealCalls++ 3800 return fmt.Errorf("reseal fails") 3801 }) 3802 defer restore() 3803 3804 reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, gadgetSnap) 3805 c.Assert(err, ErrorMatches, "cannot reseal the encryption key: reseal fails") 3806 c.Check(reboot, Equals, false) 3807 c.Check(s.bootloader.SetBootVarsCalls, Equals, 0) 3808 c.Check(resealCalls, Equals, 1) 3809 3810 m2, err := boot.ReadModeenv("") 3811 c.Assert(err, IsNil) 3812 c.Assert(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 3813 "snapd_recovery_mode=run static mocked panic=-1", 3814 "snapd_recovery_mode=run static mocked panic=-1 args from gadget", 3815 }) 3816 } 3817 3818 func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20TransitionFullExtraAndBack(c *C) { 3819 s.stampSealedKeys(c, dirs.GlobalRootDir) 3820 3821 // no command line arguments from gadget 3822 s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run static mocked panic=-1"} 3823 c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil) 3824 err := s.bootloader.SetBootVars(map[string]string{ 3825 // those are intentionally filled by the test 3826 "snapd_extra_cmdline_args": "canary", 3827 "snapd_full_cmdline_args": "canary", 3828 }) 3829 c.Assert(err, IsNil) 3830 s.bootloader.SetBootVarsCalls = 0 3831 3832 // transition to gadget with cmdline.extra 3833 sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{ 3834 {"cmdline.extra", "extra args"}, 3835 }) 3836 reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf) 3837 c.Assert(err, IsNil) 3838 c.Assert(reboot, Equals, true) 3839 c.Check(s.resealCalls, Equals, 1) 3840 c.Check(s.resealCommandLines, DeepEquals, [][]string{{ 3841 // those come from boot chains which use predictable sorting 3842 "snapd_recovery_mode=run static mocked panic=-1", 3843 "snapd_recovery_mode=run static mocked panic=-1 extra args", 3844 }}) 3845 s.resealCommandLines = nil 3846 3847 newM, err := boot.ReadModeenv("") 3848 c.Assert(err, IsNil) 3849 c.Check(newM.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 3850 "snapd_recovery_mode=run static mocked panic=-1", 3851 "snapd_recovery_mode=run static mocked panic=-1 extra args", 3852 }) 3853 c.Check(s.bootloader.SetBootVarsCalls, Equals, 1) 3854 args, err := s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args") 3855 c.Assert(err, IsNil) 3856 c.Check(args, DeepEquals, map[string]string{ 3857 "snapd_extra_cmdline_args": "extra args", 3858 // canary has been cleared 3859 "snapd_full_cmdline_args": "", 3860 }) 3861 // this normally happens after booting 3862 s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run static mocked panic=-1 extra args"} 3863 c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil) 3864 3865 // transition to full override from gadget 3866 sfFull := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{ 3867 {"cmdline.full", "full args"}, 3868 }) 3869 reboot, err = boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sfFull) 3870 c.Assert(err, IsNil) 3871 c.Assert(reboot, Equals, true) 3872 c.Check(s.resealCalls, Equals, 2) 3873 c.Check(s.resealCommandLines, DeepEquals, [][]string{{ 3874 // those come from boot chains which use predictable sorting 3875 "snapd_recovery_mode=run full args", 3876 "snapd_recovery_mode=run static mocked panic=-1 extra args", 3877 }}) 3878 s.resealCommandLines = nil 3879 // modeenv has been updated 3880 newM, err = boot.ReadModeenv("") 3881 c.Assert(err, IsNil) 3882 c.Check(newM.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 3883 "snapd_recovery_mode=run static mocked panic=-1 extra args", 3884 // new ones are appended 3885 "snapd_recovery_mode=run full args", 3886 }) 3887 // and bootloader env too 3888 args, err = s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args") 3889 c.Assert(err, IsNil) 3890 c.Check(args, DeepEquals, map[string]string{ 3891 // cleared 3892 "snapd_extra_cmdline_args": "", 3893 // and full arguments were set 3894 "snapd_full_cmdline_args": "full args", 3895 }) 3896 // this normally happens after booting 3897 s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run full args"} 3898 c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil) 3899 3900 // transition back to no arguments from the gadget 3901 sfNone := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, nil) 3902 reboot, err = boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sfNone) 3903 c.Assert(err, IsNil) 3904 c.Assert(reboot, Equals, true) 3905 c.Check(s.resealCalls, Equals, 3) 3906 c.Check(s.resealCommandLines, DeepEquals, [][]string{{ 3907 // those come from boot chains which use predictable sorting 3908 "snapd_recovery_mode=run full args", 3909 "snapd_recovery_mode=run static mocked panic=-1", 3910 }}) 3911 // modeenv has been updated again 3912 newM, err = boot.ReadModeenv("") 3913 c.Assert(err, IsNil) 3914 c.Check(newM.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 3915 "snapd_recovery_mode=run full args", 3916 // new ones are appended 3917 "snapd_recovery_mode=run static mocked panic=-1", 3918 }) 3919 // and bootloader env too 3920 args, err = s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args") 3921 c.Assert(err, IsNil) 3922 c.Check(args, DeepEquals, map[string]string{ 3923 // both env variables have been cleared 3924 "snapd_extra_cmdline_args": "", 3925 "snapd_full_cmdline_args": "", 3926 }) 3927 } 3928 3929 func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20OverSpuriousRebootsBeforeBootVarsSet(c *C) { 3930 // simulate spurious reboots 3931 s.stampSealedKeys(c, dirs.GlobalRootDir) 3932 3933 resealPanic := false 3934 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 3935 s.resealCalls++ 3936 c.Logf("reseal call %v", s.resealCalls) 3937 c.Assert(params, NotNil) 3938 c.Assert(params.ModelParams, HasLen, 1) 3939 s.resealCommandLines = append(s.resealCommandLines, params.ModelParams[0].KernelCmdlines) 3940 if resealPanic { 3941 panic("reseal panic") 3942 } 3943 return nil 3944 }) 3945 defer restore() 3946 3947 // no command line arguments from gadget 3948 s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run static mocked panic=-1"} 3949 c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil) 3950 3951 cmdlineFile := filepath.Join(c.MkDir(), "cmdline") 3952 err := ioutil.WriteFile(cmdlineFile, []byte("snapd_recovery_mode=run static mocked panic=-1"), 0644) 3953 c.Assert(err, IsNil) 3954 restore = osutil.MockProcCmdline(cmdlineFile) 3955 s.AddCleanup(restore) 3956 3957 err = s.bootloader.SetBootVars(map[string]string{ 3958 // those are intentionally filled by the test 3959 "snapd_extra_cmdline_args": "canary", 3960 "snapd_full_cmdline_args": "canary", 3961 }) 3962 c.Assert(err, IsNil) 3963 s.bootloader.SetBootVarsCalls = 0 3964 3965 restoreBootloaderNoPanic := s.bootloader.SetMockToPanic("SetBootVars") 3966 defer restoreBootloaderNoPanic() 3967 3968 // transition to gadget with cmdline.extra 3969 sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{ 3970 {"cmdline.extra", "extra args"}, 3971 }) 3972 3973 // let's panic on reseal first 3974 resealPanic = true 3975 c.Assert(func() { 3976 boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf) 3977 }, PanicMatches, "reseal panic") 3978 c.Check(s.resealCalls, Equals, 1) 3979 c.Check(s.resealCommandLines, DeepEquals, [][]string{{ 3980 // those come from boot chains which use predictable sorting 3981 "snapd_recovery_mode=run static mocked panic=-1", 3982 "snapd_recovery_mode=run static mocked panic=-1 extra args", 3983 }}) 3984 // bootenv hasn't been updated yet 3985 c.Check(s.bootloader.SetBootVarsCalls, Equals, 0) 3986 // but modeenv has already been updated 3987 m, err := boot.ReadModeenv("") 3988 c.Assert(err, IsNil) 3989 c.Check(m.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 3990 "snapd_recovery_mode=run static mocked panic=-1", 3991 "snapd_recovery_mode=run static mocked panic=-1 extra args", 3992 }) 3993 3994 // REBOOT 3995 resealPanic = false 3996 err = boot.MarkBootSuccessful(s.uc20dev) 3997 c.Assert(err, IsNil) 3998 // we resealed after reboot, since modeenv was updated and carries the 3999 // current command line only 4000 c.Check(s.resealCalls, Equals, 2) 4001 m, err = boot.ReadModeenv("") 4002 c.Assert(err, IsNil) 4003 c.Check(m.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 4004 "snapd_recovery_mode=run static mocked panic=-1", 4005 }) 4006 4007 // try the update again, but no panic in reseal this time 4008 s.resealCalls = 0 4009 s.resealCommandLines = nil 4010 resealPanic = false 4011 // but panic in set 4012 c.Assert(func() { 4013 boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf) 4014 }, PanicMatches, "mocked reboot panic in SetBootVars") 4015 c.Check(s.resealCalls, Equals, 1) 4016 c.Check(s.resealCommandLines, DeepEquals, [][]string{{ 4017 // those come from boot chains which use predictable sorting 4018 "snapd_recovery_mode=run static mocked panic=-1", 4019 "snapd_recovery_mode=run static mocked panic=-1 extra args", 4020 }}) 4021 // the call to bootloader wasn't counted, because it called panic 4022 c.Check(s.bootloader.SetBootVarsCalls, Equals, 0) 4023 m, err = boot.ReadModeenv("") 4024 c.Assert(err, IsNil) 4025 c.Check(m.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 4026 "snapd_recovery_mode=run static mocked panic=-1", 4027 "snapd_recovery_mode=run static mocked panic=-1 extra args", 4028 }) 4029 4030 // REBOOT 4031 err = boot.MarkBootSuccessful(s.uc20dev) 4032 c.Assert(err, IsNil) 4033 // we resealed after reboot again 4034 c.Check(s.resealCalls, Equals, 2) 4035 m, err = boot.ReadModeenv("") 4036 c.Assert(err, IsNil) 4037 c.Check(m.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 4038 "snapd_recovery_mode=run static mocked panic=-1", 4039 }) 4040 4041 // try again, for the last time, things should go smoothly 4042 s.resealCalls = 0 4043 s.resealCommandLines = nil 4044 restoreBootloaderNoPanic() 4045 reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf) 4046 c.Assert(err, IsNil) 4047 c.Check(reboot, Equals, true) 4048 c.Check(s.resealCalls, Equals, 1) 4049 c.Check(s.bootloader.SetBootVarsCalls, Equals, 1) 4050 // all done, modeenv 4051 m, err = boot.ReadModeenv("") 4052 c.Assert(err, IsNil) 4053 c.Check(m.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 4054 "snapd_recovery_mode=run static mocked panic=-1", 4055 "snapd_recovery_mode=run static mocked panic=-1 extra args", 4056 }) 4057 args, err := s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args") 4058 c.Assert(err, IsNil) 4059 c.Check(args, DeepEquals, map[string]string{ 4060 "snapd_extra_cmdline_args": "extra args", 4061 // canary has been cleared 4062 "snapd_full_cmdline_args": "", 4063 }) 4064 } 4065 4066 func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20OverSpuriousRebootsAfterBootVars(c *C) { 4067 // simulate spurious reboots 4068 s.stampSealedKeys(c, dirs.GlobalRootDir) 4069 4070 // no command line arguments from gadget 4071 s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run static mocked panic=-1"} 4072 c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil) 4073 4074 cmdlineFile := filepath.Join(c.MkDir(), "cmdline") 4075 restore := osutil.MockProcCmdline(cmdlineFile) 4076 s.AddCleanup(restore) 4077 4078 err := s.bootloader.SetBootVars(map[string]string{ 4079 // those are intentionally filled by the test 4080 "snapd_extra_cmdline_args": "canary", 4081 "snapd_full_cmdline_args": "canary", 4082 }) 4083 c.Assert(err, IsNil) 4084 s.bootloader.SetBootVarsCalls = 0 4085 4086 // transition to gadget with cmdline.extra 4087 sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{ 4088 {"cmdline.extra", "extra args"}, 4089 }) 4090 4091 // let's panic after setting bootenv, but before returning, such that if 4092 // executed by a task handler, the task's status would not get updated 4093 s.bootloader.SetErrFunc = func() error { 4094 panic("mocked reboot panic after SetBootVars") 4095 } 4096 c.Assert(func() { 4097 boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf) 4098 }, PanicMatches, "mocked reboot panic after SetBootVars") 4099 c.Check(s.resealCalls, Equals, 1) 4100 c.Check(s.resealCommandLines, DeepEquals, [][]string{{ 4101 // those come from boot chains which use predictable sorting 4102 "snapd_recovery_mode=run static mocked panic=-1", 4103 "snapd_recovery_mode=run static mocked panic=-1 extra args", 4104 }}) 4105 // the call to bootloader was executed 4106 c.Check(s.bootloader.SetBootVarsCalls, Equals, 1) 4107 m, err := boot.ReadModeenv("") 4108 c.Assert(err, IsNil) 4109 c.Check(m.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 4110 "snapd_recovery_mode=run static mocked panic=-1", 4111 "snapd_recovery_mode=run static mocked panic=-1 extra args", 4112 }) 4113 args, err := s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args") 4114 c.Assert(err, IsNil) 4115 c.Check(args, DeepEquals, map[string]string{ 4116 "snapd_extra_cmdline_args": "extra args", 4117 // canary has been cleared 4118 "snapd_full_cmdline_args": "", 4119 }) 4120 4121 // REBOOT; since we rebooted after updating the bootenv, the kernel 4122 // command line will include arguments that came from gadget snap 4123 s.bootloader.SetBootVarsCalls = 0 4124 s.resealCalls = 0 4125 err = ioutil.WriteFile(cmdlineFile, []byte("snapd_recovery_mode=run static mocked panic=-1 extra args"), 0644) 4126 c.Assert(err, IsNil) 4127 err = boot.MarkBootSuccessful(s.uc20dev) 4128 c.Assert(err, IsNil) 4129 // we resealed after reboot again 4130 c.Check(s.resealCalls, Equals, 1) 4131 // bootenv wasn't touched 4132 c.Check(s.bootloader.SetBootVarsCalls, Equals, 0) 4133 m, err = boot.ReadModeenv("") 4134 c.Assert(err, IsNil) 4135 c.Check(m.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 4136 "snapd_recovery_mode=run static mocked panic=-1 extra args", 4137 }) 4138 4139 // try again, as if the task handler gets to run again 4140 s.resealCalls = 0 4141 reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf) 4142 c.Assert(err, IsNil) 4143 // nothing changed now, we already booted with the new command line 4144 c.Check(reboot, Equals, false) 4145 // not reseal since nothing changed 4146 c.Check(s.resealCalls, Equals, 0) 4147 // no changes to the bootenv either 4148 c.Check(s.bootloader.SetBootVarsCalls, Equals, 0) 4149 // all done, modeenv 4150 m, err = boot.ReadModeenv("") 4151 c.Assert(err, IsNil) 4152 c.Check(m.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{ 4153 "snapd_recovery_mode=run static mocked panic=-1 extra args", 4154 }) 4155 args, err = s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args") 4156 c.Assert(err, IsNil) 4157 c.Check(args, DeepEquals, map[string]string{ 4158 "snapd_extra_cmdline_args": "extra args", 4159 "snapd_full_cmdline_args": "", 4160 }) 4161 }