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