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