github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019-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 main_test 21 22 import ( 23 "bytes" 24 "encoding/json" 25 "fmt" 26 "io/ioutil" 27 "os" 28 "path/filepath" 29 "strings" 30 "time" 31 32 . "gopkg.in/check.v1" 33 34 "github.com/snapcore/snapd/asserts" 35 "github.com/snapcore/snapd/asserts/assertstest" 36 "github.com/snapcore/snapd/boot" 37 "github.com/snapcore/snapd/boot/boottest" 38 "github.com/snapcore/snapd/bootloader" 39 "github.com/snapcore/snapd/bootloader/bootloadertest" 40 main "github.com/snapcore/snapd/cmd/snap-bootstrap" 41 "github.com/snapcore/snapd/dirs" 42 "github.com/snapcore/snapd/logger" 43 "github.com/snapcore/snapd/osutil" 44 "github.com/snapcore/snapd/osutil/disks" 45 "github.com/snapcore/snapd/secboot" 46 "github.com/snapcore/snapd/seed" 47 "github.com/snapcore/snapd/seed/seedtest" 48 "github.com/snapcore/snapd/snap" 49 "github.com/snapcore/snapd/systemd" 50 "github.com/snapcore/snapd/testutil" 51 ) 52 53 var brandPrivKey, _ = assertstest.GenerateKey(752) 54 55 type initramfsMountsSuite struct { 56 testutil.BaseTest 57 58 // makes available a bunch of helper (like MakeAssertedSnap) 59 *seedtest.TestingSeed20 60 61 Stdout *bytes.Buffer 62 logs *bytes.Buffer 63 64 seedDir string 65 sysLabel string 66 model *asserts.Model 67 tmpDir string 68 69 snapDeclAssertsTime time.Time 70 71 kernel snap.PlaceInfo 72 kernelr2 snap.PlaceInfo 73 core20 snap.PlaceInfo 74 core20r2 snap.PlaceInfo 75 snapd snap.PlaceInfo 76 } 77 78 var _ = Suite(&initramfsMountsSuite{}) 79 80 var ( 81 tmpfsMountOpts = &main.SystemdMountOptions{ 82 Tmpfs: true, 83 NoSuid: true, 84 } 85 needsFsckDiskMountOpts = &main.SystemdMountOptions{ 86 NeedsFsck: true, 87 } 88 needsFsckAndNoSuidDiskMountOpts = &main.SystemdMountOptions{ 89 NeedsFsck: true, 90 NoSuid: true, 91 } 92 needsNoSuidDiskMountOpts = &main.SystemdMountOptions{ 93 NoSuid: true, 94 } 95 96 // a boot disk without ubuntu-save 97 defaultBootDisk = &disks.MockDiskMapping{ 98 FilesystemLabelToPartUUID: map[string]string{ 99 "ubuntu-boot": "ubuntu-boot-partuuid", 100 "ubuntu-seed": "ubuntu-seed-partuuid", 101 "ubuntu-data": "ubuntu-data-partuuid", 102 }, 103 DiskHasPartitions: true, 104 DevNum: "default", 105 } 106 107 defaultBootWithSaveDisk = &disks.MockDiskMapping{ 108 FilesystemLabelToPartUUID: map[string]string{ 109 "ubuntu-boot": "ubuntu-boot-partuuid", 110 "ubuntu-seed": "ubuntu-seed-partuuid", 111 "ubuntu-data": "ubuntu-data-partuuid", 112 "ubuntu-save": "ubuntu-save-partuuid", 113 }, 114 DiskHasPartitions: true, 115 DevNum: "default-with-save", 116 } 117 118 defaultEncBootDisk = &disks.MockDiskMapping{ 119 FilesystemLabelToPartUUID: map[string]string{ 120 "ubuntu-boot": "ubuntu-boot-partuuid", 121 "ubuntu-seed": "ubuntu-seed-partuuid", 122 "ubuntu-data-enc": "ubuntu-data-enc-partuuid", 123 "ubuntu-save-enc": "ubuntu-save-enc-partuuid", 124 }, 125 DiskHasPartitions: true, 126 DevNum: "defaultEncDev", 127 } 128 129 mockStateContent = `{"data":{"auth":{"users":[{"id":1,"name":"mvo"}],"macaroon-key":"not-a-cookie","last-id":1}},"some":{"other":"stuff"}}` 130 ) 131 132 func (s *initramfsMountsSuite) setupSeed(c *C, modelAssertTime time.Time, gadgetSnapFiles [][]string) { 133 // pretend /run/mnt/ubuntu-seed has a valid seed 134 s.seedDir = boot.InitramfsUbuntuSeedDir 135 136 // now create a minimal uc20 seed dir with snaps/assertions 137 seed20 := &seedtest.TestingSeed20{SeedDir: s.seedDir} 138 seed20.SetupAssertSigning("canonical") 139 restore := seed.MockTrusted(seed20.StoreSigning.Trusted) 140 s.AddCleanup(restore) 141 142 // XXX: we don't really use this but seedtest always expects my-brand 143 seed20.Brands.Register("my-brand", brandPrivKey, map[string]interface{}{ 144 "verification": "verified", 145 }) 146 147 // make sure all the assertions use the same time 148 seed20.SetSnapAssertionNow(s.snapDeclAssertsTime) 149 150 // add a bunch of snaps 151 seed20.MakeAssertedSnap(c, "name: snapd\nversion: 1\ntype: snapd", nil, snap.R(1), "canonical", seed20.StoreSigning.Database) 152 seed20.MakeAssertedSnap(c, "name: pc\nversion: 1\ntype: gadget\nbase: core20", gadgetSnapFiles, snap.R(1), "canonical", seed20.StoreSigning.Database) 153 seed20.MakeAssertedSnap(c, "name: pc-kernel\nversion: 1\ntype: kernel", nil, snap.R(1), "canonical", seed20.StoreSigning.Database) 154 seed20.MakeAssertedSnap(c, "name: core20\nversion: 1\ntype: base", nil, snap.R(1), "canonical", seed20.StoreSigning.Database) 155 156 // pretend that by default, the model uses an older timestamp than the 157 // snap assertions 158 if modelAssertTime.IsZero() { 159 modelAssertTime = s.snapDeclAssertsTime.Add(-30 * time.Minute) 160 } 161 162 s.sysLabel = "20191118" 163 s.model = seed20.MakeSeed(c, s.sysLabel, "my-brand", "my-model", map[string]interface{}{ 164 "display-name": "my model", 165 "architecture": "amd64", 166 "base": "core20", 167 "timestamp": modelAssertTime.Format(time.RFC3339), 168 "snaps": []interface{}{ 169 map[string]interface{}{ 170 "name": "pc-kernel", 171 "id": seed20.AssertedSnapID("pc-kernel"), 172 "type": "kernel", 173 "default-channel": "20", 174 }, 175 map[string]interface{}{ 176 "name": "pc", 177 "id": seed20.AssertedSnapID("pc"), 178 "type": "gadget", 179 "default-channel": "20", 180 }}, 181 }, nil) 182 } 183 184 func (s *initramfsMountsSuite) SetUpTest(c *C) { 185 s.BaseTest.SetUpTest(c) 186 187 s.Stdout = bytes.NewBuffer(nil) 188 189 buf, restore := logger.MockLogger() 190 s.AddCleanup(restore) 191 s.logs = buf 192 193 s.tmpDir = c.MkDir() 194 195 // mock /run/mnt 196 dirs.SetRootDir(s.tmpDir) 197 restore = func() { dirs.SetRootDir("") } 198 s.AddCleanup(restore) 199 200 // use a specific time for all the assertions, in the future so that we can 201 // set the timestamp of the model assertion to something newer than now, but 202 // still older than the snap declarations by default 203 s.snapDeclAssertsTime = time.Now().Add(60 * time.Minute) 204 205 // setup the seed 206 s.setupSeed(c, time.Time{}, nil) 207 208 // make test snap PlaceInfo's for various boot functionality 209 var err error 210 s.kernel, err = snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap") 211 c.Assert(err, IsNil) 212 213 s.core20, err = snap.ParsePlaceInfoFromSnapFileName("core20_1.snap") 214 c.Assert(err, IsNil) 215 216 s.kernelr2, err = snap.ParsePlaceInfoFromSnapFileName("pc-kernel_2.snap") 217 c.Assert(err, IsNil) 218 219 s.core20r2, err = snap.ParsePlaceInfoFromSnapFileName("core20_2.snap") 220 c.Assert(err, IsNil) 221 222 s.snapd, err = snap.ParsePlaceInfoFromSnapFileName("snapd_1.snap") 223 c.Assert(err, IsNil) 224 225 // by default mock that we don't have UEFI vars, etc. to get the booted 226 // kernel partition partition uuid 227 s.AddCleanup(main.MockPartitionUUIDForBootedKernelDisk("")) 228 s.AddCleanup(main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 229 return nil 230 })) 231 s.AddCleanup(main.MockSecbootMeasureSnapModelWhenPossible(func(f func() (*asserts.Model, error)) error { 232 c.Check(f, NotNil) 233 return nil 234 })) 235 s.AddCleanup(main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 236 return foundUnencrypted(name), nil 237 })) 238 s.AddCleanup(main.MockSecbootLockSealedKeys(func() error { 239 return nil 240 })) 241 242 s.AddCleanup(main.MockOsutilSetTime(func(time.Time) error { 243 return nil 244 })) 245 } 246 247 // static test cases for time test variants shared across the different modes 248 249 type timeTestCase struct { 250 now time.Time 251 modelTime time.Time 252 expT time.Time 253 setTimeCalls int 254 comment string 255 } 256 257 func (s *initramfsMountsSuite) timeTestCases() []timeTestCase { 258 // epoch time 259 epoch := time.Time{} 260 261 // t1 is the kernel initrd build time 262 t1 := s.snapDeclAssertsTime.Add(-30 * 24 * time.Hour) 263 // technically there is another time here between t1 and t2, that is the 264 // default model sign time, but since it's older than the snap assertion 265 // sign time (t2) it's not actually used in the test 266 267 // t2 is the time that snap-revision / snap-declaration assertions will be 268 // signed with 269 t2 := s.snapDeclAssertsTime 270 271 // t3 is a time after the snap-declarations are signed 272 t3 := s.snapDeclAssertsTime.Add(30 * 24 * time.Hour) 273 274 // t4 and t5 are both times after the the snap declarations are signed 275 t4 := s.snapDeclAssertsTime.Add(60 * 24 * time.Hour) 276 t5 := s.snapDeclAssertsTime.Add(120 * 24 * time.Hour) 277 278 return []timeTestCase{ 279 { 280 now: epoch, 281 expT: t2, 282 setTimeCalls: 1, 283 comment: "now() is epoch", 284 }, 285 { 286 now: t1, 287 expT: t2, 288 setTimeCalls: 1, 289 comment: "now() is kernel initrd sign time", 290 }, 291 { 292 now: t3, 293 expT: t3, 294 setTimeCalls: 0, 295 comment: "now() is newer than snap assertion", 296 }, 297 { 298 now: t3, 299 modelTime: t4, 300 expT: t4, 301 setTimeCalls: 1, 302 comment: "model time is newer than now(), which is newer than snap asserts", 303 }, 304 { 305 now: t5, 306 modelTime: t4, 307 expT: t5, 308 setTimeCalls: 0, 309 comment: "model time is newest, but older than now()", 310 }, 311 } 312 } 313 314 // helpers to create consistent UnlockResult values 315 316 func foundUnencrypted(name string) secboot.UnlockResult { 317 dev := filepath.Join("/dev/disk/by-partuuid", name+"-partuuid") 318 return secboot.UnlockResult{ 319 PartDevice: dev, 320 FsDevice: dev, 321 } 322 } 323 324 func happyUnlocked(name string, method secboot.UnlockMethod) secboot.UnlockResult { 325 return secboot.UnlockResult{ 326 PartDevice: filepath.Join("/dev/disk/by-partuuid", name+"-enc-partuuid"), 327 FsDevice: filepath.Join("/dev/mapper", name+"-random"), 328 IsEncrypted: true, 329 UnlockMethod: method, 330 } 331 } 332 333 func foundEncrypted(name string) secboot.UnlockResult { 334 return secboot.UnlockResult{ 335 PartDevice: filepath.Join("/dev/disk/by-partuuid", name+"-enc-partuuid"), 336 // FsDevice is empty if we didn't unlock anything 337 FsDevice: "", 338 IsEncrypted: true, 339 } 340 } 341 342 func notFoundPart() secboot.UnlockResult { 343 return secboot.UnlockResult{} 344 } 345 346 // makeSnapFilesOnEarlyBootUbuntuData creates the snap files on ubuntu-data as 347 // we 348 func makeSnapFilesOnEarlyBootUbuntuData(c *C, snaps ...snap.PlaceInfo) { 349 snapDir := dirs.SnapBlobDirUnder(boot.InitramfsWritableDir) 350 err := os.MkdirAll(snapDir, 0755) 351 c.Assert(err, IsNil) 352 for _, sn := range snaps { 353 snFilename := sn.Filename() 354 err = ioutil.WriteFile(filepath.Join(snapDir, snFilename), nil, 0644) 355 c.Assert(err, IsNil) 356 } 357 } 358 359 func (s *initramfsMountsSuite) mockProcCmdlineContent(c *C, newContent string) { 360 mockProcCmdline := filepath.Join(c.MkDir(), "proc-cmdline") 361 err := ioutil.WriteFile(mockProcCmdline, []byte(newContent), 0644) 362 c.Assert(err, IsNil) 363 restore := osutil.MockProcCmdline(mockProcCmdline) 364 s.AddCleanup(restore) 365 } 366 367 func (s *initramfsMountsSuite) mockUbuntuSaveKeyAndMarker(c *C, rootDir, key, marker string) { 368 keyPath := filepath.Join(dirs.SnapFDEDirUnder(rootDir), "ubuntu-save.key") 369 c.Assert(os.MkdirAll(filepath.Dir(keyPath), 0700), IsNil) 370 c.Assert(ioutil.WriteFile(keyPath, []byte(key), 0600), IsNil) 371 372 if marker != "" { 373 markerPath := filepath.Join(dirs.SnapFDEDirUnder(rootDir), "marker") 374 c.Assert(ioutil.WriteFile(markerPath, []byte(marker), 0600), IsNil) 375 } 376 } 377 378 func (s *initramfsMountsSuite) mockUbuntuSaveMarker(c *C, rootDir, marker string) { 379 markerPath := filepath.Join(rootDir, "device/fde", "marker") 380 c.Assert(os.MkdirAll(filepath.Dir(markerPath), 0700), IsNil) 381 c.Assert(ioutil.WriteFile(markerPath, []byte(marker), 0600), IsNil) 382 } 383 384 func (s *initramfsMountsSuite) TestInitramfsMountsNoModeError(c *C) { 385 s.mockProcCmdlineContent(c, "nothing-to-see") 386 387 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 388 c.Assert(err, ErrorMatches, "cannot detect mode nor recovery system to use") 389 } 390 391 func (s *initramfsMountsSuite) TestInitramfsMountsUnknownMode(c *C) { 392 s.mockProcCmdlineContent(c, "snapd_recovery_mode=install-foo") 393 394 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 395 c.Assert(err, ErrorMatches, `cannot use unknown mode "install-foo"`) 396 } 397 398 type systemdMount struct { 399 what string 400 where string 401 opts *main.SystemdMountOptions 402 } 403 404 // this is a function so we evaluate InitramfsUbuntuBootDir, etc at the time of 405 // the test to pick up test-specific dirs.GlobalRootDir 406 func ubuntuLabelMount(label string, mode string) systemdMount { 407 mnt := systemdMount{ 408 opts: needsFsckDiskMountOpts, 409 } 410 switch label { 411 case "ubuntu-boot": 412 mnt.what = "/dev/disk/by-label/ubuntu-boot" 413 mnt.where = boot.InitramfsUbuntuBootDir 414 case "ubuntu-seed": 415 mnt.what = "/dev/disk/by-label/ubuntu-seed" 416 mnt.where = boot.InitramfsUbuntuSeedDir 417 // don't fsck in run mode 418 if mode == "run" { 419 mnt.opts = nil 420 } 421 case "ubuntu-data": 422 mnt.what = "/dev/disk/by-label/ubuntu-data" 423 mnt.where = boot.InitramfsDataDir 424 mnt.opts = needsFsckAndNoSuidDiskMountOpts 425 } 426 427 return mnt 428 } 429 430 // ubuntuPartUUIDMount returns a systemdMount for the partuuid disk, expecting 431 // that the partuuid contains in it the expected label for easier coding 432 func ubuntuPartUUIDMount(partuuid string, mode string) systemdMount { 433 // all partitions are expected to be mounted with fsck on 434 mnt := systemdMount{ 435 opts: needsFsckDiskMountOpts, 436 } 437 mnt.what = filepath.Join("/dev/disk/by-partuuid", partuuid) 438 switch { 439 case strings.Contains(partuuid, "ubuntu-boot"): 440 mnt.where = boot.InitramfsUbuntuBootDir 441 case strings.Contains(partuuid, "ubuntu-seed"): 442 mnt.where = boot.InitramfsUbuntuSeedDir 443 case strings.Contains(partuuid, "ubuntu-data"): 444 mnt.where = boot.InitramfsDataDir 445 mnt.opts = needsFsckAndNoSuidDiskMountOpts 446 case strings.Contains(partuuid, "ubuntu-save"): 447 mnt.where = boot.InitramfsUbuntuSaveDir 448 } 449 450 return mnt 451 } 452 453 func (s *initramfsMountsSuite) makeSeedSnapSystemdMount(typ snap.Type) systemdMount { 454 mnt := systemdMount{} 455 var name, dir string 456 switch typ { 457 case snap.TypeSnapd: 458 name = "snapd" 459 dir = "snapd" 460 case snap.TypeBase: 461 name = "core20" 462 dir = "base" 463 case snap.TypeKernel: 464 name = "pc-kernel" 465 dir = "kernel" 466 } 467 mnt.what = filepath.Join(s.seedDir, "snaps", name+"_1.snap") 468 mnt.where = filepath.Join(boot.InitramfsRunMntDir, dir) 469 470 return mnt 471 } 472 473 func (s *initramfsMountsSuite) makeRunSnapSystemdMount(typ snap.Type, sn snap.PlaceInfo) systemdMount { 474 mnt := systemdMount{} 475 var dir string 476 switch typ { 477 case snap.TypeSnapd: 478 dir = "snapd" 479 case snap.TypeBase: 480 dir = "base" 481 case snap.TypeKernel: 482 dir = "kernel" 483 } 484 485 mnt.what = filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), sn.Filename()) 486 mnt.where = filepath.Join(boot.InitramfsRunMntDir, dir) 487 488 return mnt 489 } 490 491 func (s *initramfsMountsSuite) mockSystemdMountSequence(c *C, mounts []systemdMount, comment CommentInterface) (restore func()) { 492 n := 0 493 if comment == nil { 494 comment = Commentf("") 495 } 496 s.AddCleanup(func() { 497 // make sure that after the test is done, we had as many mount calls as 498 // mocked mounts 499 c.Check(n, Equals, len(mounts), comment) 500 }) 501 return main.MockSystemdMount(func(what, where string, opts *main.SystemdMountOptions) error { 502 n++ 503 c.Assert(n <= len(mounts), Equals, true) 504 if n > len(mounts) { 505 return fmt.Errorf("unexpected systemd-mount call: %s, %s, %+v", what, where, opts) 506 } 507 mnt := mounts[n-1] 508 c.Assert(what, Equals, mnt.what, comment) 509 c.Assert(where, Equals, mnt.where, comment) 510 c.Assert(opts, DeepEquals, mnt.opts, Commentf("what is %s, where is %s, comment is %s", what, where, comment)) 511 return nil 512 }) 513 } 514 515 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeHappy(c *C) { 516 s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel) 517 518 // ensure that we check that access to sealed keys were locked 519 sealedKeysLocked := false 520 defer main.MockSecbootLockSealedKeys(func() error { 521 sealedKeysLocked = true 522 return nil 523 })() 524 525 restore := s.mockSystemdMountSequence(c, []systemdMount{ 526 ubuntuLabelMount("ubuntu-seed", "install"), 527 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 528 s.makeSeedSnapSystemdMount(snap.TypeKernel), 529 s.makeSeedSnapSystemdMount(snap.TypeBase), 530 { 531 "tmpfs", 532 boot.InitramfsDataDir, 533 tmpfsMountOpts, 534 }, 535 }, nil) 536 defer restore() 537 538 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 539 c.Assert(err, IsNil) 540 541 modeEnv := dirs.SnapModeenvFileUnder(boot.InitramfsWritableDir) 542 c.Check(modeEnv, testutil.FileEquals, `mode=install 543 recovery_system=20191118 544 base=core20_1.snap 545 model=my-brand/my-model 546 grade=signed 547 `) 548 cloudInitDisable := filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled") 549 c.Check(cloudInitDisable, testutil.FilePresent) 550 551 c.Check(sealedKeysLocked, Equals, true) 552 } 553 554 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeBootFlagsSet(c *C) { 555 s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel) 556 557 tt := []struct { 558 bootFlags string 559 expBootFlagsFile string 560 }{ 561 { 562 "factory", 563 "factory", 564 }, 565 { 566 "factory,,,,", 567 "factory", 568 }, 569 { 570 "factory,,,,unknown-new-flag", 571 "factory,unknown-new-flag", 572 }, 573 { 574 "", 575 "", 576 }, 577 } 578 579 for _, t := range tt { 580 restore := s.mockSystemdMountSequence(c, []systemdMount{ 581 ubuntuLabelMount("ubuntu-seed", "install"), 582 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 583 s.makeSeedSnapSystemdMount(snap.TypeKernel), 584 s.makeSeedSnapSystemdMount(snap.TypeBase), 585 { 586 "tmpfs", 587 boot.InitramfsDataDir, 588 tmpfsMountOpts, 589 }, 590 }, nil) 591 defer restore() 592 593 // mock a bootloader 594 bl := bootloadertest.Mock("bootloader", c.MkDir()) 595 err := bl.SetBootVars(map[string]string{ 596 "snapd_boot_flags": t.bootFlags, 597 }) 598 c.Assert(err, IsNil) 599 bootloader.Force(bl) 600 defer bootloader.Force(nil) 601 602 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 603 c.Assert(err, IsNil) 604 605 // check that we wrote the /run file with the boot flags in it 606 c.Assert(filepath.Join(dirs.SnapRunDir, "boot-flags"), testutil.FileEquals, t.expBootFlagsFile) 607 } 608 } 609 610 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeBootFlagsSet(c *C) { 611 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 612 613 tt := []struct { 614 bootFlags []string 615 expBootFlagsFile string 616 }{ 617 { 618 []string{"factory"}, 619 "factory", 620 }, 621 { 622 []string{"factory", ""}, 623 "factory", 624 }, 625 { 626 []string{"factory", "unknown-new-flag"}, 627 "factory,unknown-new-flag", 628 }, 629 { 630 []string{}, 631 "", 632 }, 633 } 634 635 for _, t := range tt { 636 restore := disks.MockMountPointDisksToPartitionMapping( 637 map[disks.Mountpoint]*disks.MockDiskMapping{ 638 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootWithSaveDisk, 639 {Mountpoint: boot.InitramfsDataDir}: defaultBootWithSaveDisk, 640 {Mountpoint: boot.InitramfsUbuntuSaveDir}: defaultBootWithSaveDisk, 641 }, 642 ) 643 defer restore() 644 645 restore = s.mockSystemdMountSequence(c, []systemdMount{ 646 ubuntuLabelMount("ubuntu-boot", "run"), 647 ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"), 648 ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"), 649 ubuntuPartUUIDMount("ubuntu-save-partuuid", "run"), 650 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 651 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 652 }, nil) 653 defer restore() 654 655 // mock a bootloader 656 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 657 bootloader.Force(bloader) 658 defer bootloader.Force(nil) 659 660 // set the current kernel 661 restore = bloader.SetEnabledKernel(s.kernel) 662 defer restore() 663 664 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 665 666 // write modeenv with boot flags 667 modeEnv := boot.Modeenv{ 668 Mode: "run", 669 Base: s.core20.Filename(), 670 CurrentKernels: []string{s.kernel.Filename()}, 671 BootFlags: t.bootFlags, 672 } 673 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 674 c.Assert(err, IsNil) 675 676 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 677 c.Assert(err, IsNil) 678 679 // check that we wrote the /run file with the boot flags in it 680 c.Assert(filepath.Join(dirs.SnapRunDir, "boot-flags"), testutil.FileEquals, t.expBootFlagsFile) 681 } 682 } 683 684 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeTimeMovesForwardHappy(c *C) { 685 s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel) 686 687 for _, tc := range s.timeTestCases() { 688 comment := Commentf(tc.comment) 689 cleanups := []func(){} 690 691 // always remove the ubuntu-seed dir, otherwise setupSeed complains the 692 // model file already exists and can't setup the seed 693 err := os.RemoveAll(filepath.Join(boot.InitramfsUbuntuSeedDir)) 694 c.Assert(err, IsNil, comment) 695 s.setupSeed(c, tc.modelTime, nil) 696 697 restore := main.MockTimeNow(func() time.Time { 698 return tc.now 699 }) 700 cleanups = append(cleanups, restore) 701 osutilSetTimeCalls := 0 702 703 // check what time we try to move forward to 704 restore = main.MockOsutilSetTime(func(t time.Time) error { 705 osutilSetTimeCalls++ 706 // make sure the timestamps are within 1 second of each other, they 707 // won't be equal since the timestamp is serialized to an assertion and 708 // read back 709 tTrunc := t.Truncate(2 * time.Second) 710 expTTrunc := tc.expT.Truncate(2 * time.Second) 711 c.Assert(tTrunc.Equal(expTTrunc), Equals, true, Commentf("%s, exp %s, got %s", tc.comment, t, s.snapDeclAssertsTime)) 712 return nil 713 }) 714 cleanups = append(cleanups, restore) 715 716 restore = s.mockSystemdMountSequence(c, []systemdMount{ 717 ubuntuLabelMount("ubuntu-seed", "install"), 718 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 719 s.makeSeedSnapSystemdMount(snap.TypeKernel), 720 s.makeSeedSnapSystemdMount(snap.TypeBase), 721 { 722 "tmpfs", 723 boot.InitramfsDataDir, 724 tmpfsMountOpts, 725 }, 726 }, nil) 727 cleanups = append(cleanups, restore) 728 729 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 730 c.Assert(err, IsNil, comment) 731 732 c.Assert(osutilSetTimeCalls, Equals, tc.setTimeCalls) 733 734 for _, r := range cleanups { 735 r() 736 } 737 } 738 } 739 740 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeGadgetDefaultsHappy(c *C) { 741 // setup a seed with default gadget yaml 742 const gadgetYamlDefaults = ` 743 defaults: 744 system: 745 service: 746 rsyslog.disable: true 747 ssh.disable: true 748 console-conf.disable: true 749 journal.persistent: true 750 ` 751 c.Assert(os.RemoveAll(s.seedDir), IsNil) 752 753 s.setupSeed(c, time.Time{}, [][]string{ 754 {"meta/gadget.yaml", gadgetYamlDefaults}, 755 }) 756 757 s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel) 758 759 restore := s.mockSystemdMountSequence(c, []systemdMount{ 760 ubuntuLabelMount("ubuntu-seed", "install"), 761 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 762 s.makeSeedSnapSystemdMount(snap.TypeKernel), 763 s.makeSeedSnapSystemdMount(snap.TypeBase), 764 { 765 "tmpfs", 766 boot.InitramfsDataDir, 767 tmpfsMountOpts, 768 }, 769 }, nil) 770 defer restore() 771 772 // we will call out to systemctl in the initramfs, but only using --root 773 // which doesn't talk to systemd, just manipulates files around 774 var sysctlArgs [][]string 775 systemctlRestorer := systemd.MockSystemctl(func(args ...string) (buf []byte, err error) { 776 sysctlArgs = append(sysctlArgs, args) 777 return nil, nil 778 }) 779 defer systemctlRestorer() 780 781 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 782 c.Assert(err, IsNil) 783 784 modeEnv := dirs.SnapModeenvFileUnder(boot.InitramfsWritableDir) 785 c.Check(modeEnv, testutil.FileEquals, `mode=install 786 recovery_system=20191118 787 base=core20_1.snap 788 model=my-brand/my-model 789 grade=signed 790 `) 791 792 cloudInitDisable := filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled") 793 c.Check(cloudInitDisable, testutil.FilePresent) 794 795 // check that everything from the gadget defaults was setup 796 c.Assert(osutil.FileExists(filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/ssh/sshd_not_to_be_run")), Equals, true) 797 c.Assert(osutil.FileExists(filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/var/lib/console-conf/complete")), Equals, true) 798 exists, _, _ := osutil.DirExists(filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/var/log/journal")) 799 c.Assert(exists, Equals, true) 800 801 // systemctl was called the way we expect 802 c.Assert(sysctlArgs, DeepEquals, [][]string{{"--root", filepath.Join(boot.InitramfsWritableDir, "_writable_defaults"), "mask", "rsyslog.service"}}) 803 } 804 805 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeBootedKernelPartitionUUIDHappy(c *C) { 806 s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel) 807 808 restore := main.MockPartitionUUIDForBootedKernelDisk("specific-ubuntu-seed-partuuid") 809 defer restore() 810 811 restore = s.mockSystemdMountSequence(c, []systemdMount{ 812 { 813 "/dev/disk/by-partuuid/specific-ubuntu-seed-partuuid", 814 boot.InitramfsUbuntuSeedDir, 815 needsFsckDiskMountOpts, 816 }, 817 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 818 s.makeSeedSnapSystemdMount(snap.TypeKernel), 819 s.makeSeedSnapSystemdMount(snap.TypeBase), 820 { 821 "tmpfs", 822 boot.InitramfsDataDir, 823 tmpfsMountOpts, 824 }, 825 }, nil) 826 defer restore() 827 828 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 829 c.Assert(err, IsNil) 830 831 modeEnv := dirs.SnapModeenvFileUnder(boot.InitramfsWritableDir) 832 c.Check(modeEnv, testutil.FileEquals, `mode=install 833 recovery_system=20191118 834 base=core20_1.snap 835 model=my-brand/my-model 836 grade=signed 837 `) 838 cloudInitDisable := filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled") 839 c.Check(cloudInitDisable, testutil.FilePresent) 840 } 841 842 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeUnencryptedWithSaveHappy(c *C) { 843 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 844 845 restore := disks.MockMountPointDisksToPartitionMapping( 846 map[disks.Mountpoint]*disks.MockDiskMapping{ 847 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootWithSaveDisk, 848 {Mountpoint: boot.InitramfsDataDir}: defaultBootWithSaveDisk, 849 {Mountpoint: boot.InitramfsUbuntuSaveDir}: defaultBootWithSaveDisk, 850 }, 851 ) 852 defer restore() 853 854 restore = s.mockSystemdMountSequence(c, []systemdMount{ 855 ubuntuLabelMount("ubuntu-boot", "run"), 856 ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"), 857 ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"), 858 ubuntuPartUUIDMount("ubuntu-save-partuuid", "run"), 859 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 860 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 861 }, nil) 862 defer restore() 863 864 // mock a bootloader 865 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 866 bootloader.Force(bloader) 867 defer bootloader.Force(nil) 868 869 // set the current kernel 870 restore = bloader.SetEnabledKernel(s.kernel) 871 defer restore() 872 873 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 874 875 // write modeenv 876 modeEnv := boot.Modeenv{ 877 Mode: "run", 878 Base: s.core20.Filename(), 879 CurrentKernels: []string{s.kernel.Filename()}, 880 } 881 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 882 c.Assert(err, IsNil) 883 884 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 885 c.Assert(err, IsNil) 886 } 887 888 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeTimeMovesForwardHappy(c *C) { 889 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 890 891 for _, isFirstBoot := range []bool{true, false} { 892 for _, tc := range s.timeTestCases() { 893 comment := Commentf(tc.comment) 894 cleanups := []func(){} 895 896 // always remove the ubuntu-seed dir, otherwise setupSeed complains the 897 // model file already exists and can't setup the seed 898 err := os.RemoveAll(filepath.Join(boot.InitramfsUbuntuSeedDir)) 899 c.Assert(err, IsNil, comment) 900 s.setupSeed(c, tc.modelTime, nil) 901 902 restore := main.MockTimeNow(func() time.Time { 903 return tc.now 904 }) 905 cleanups = append(cleanups, restore) 906 907 restore = disks.MockMountPointDisksToPartitionMapping( 908 map[disks.Mountpoint]*disks.MockDiskMapping{ 909 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootDisk, 910 {Mountpoint: boot.InitramfsDataDir}: defaultBootDisk, 911 }, 912 ) 913 cleanups = append(cleanups, restore) 914 915 osutilSetTimeCalls := 0 916 917 // check what time we try to move forward to 918 restore = main.MockOsutilSetTime(func(t time.Time) error { 919 osutilSetTimeCalls++ 920 // make sure the timestamps are within 1 second of each other, they 921 // won't be equal since the timestamp is serialized to an assertion and 922 // read back 923 tTrunc := t.Truncate(2 * time.Second) 924 expTTrunc := tc.expT.Truncate(2 * time.Second) 925 c.Assert(tTrunc.Equal(expTTrunc), Equals, true, Commentf("%s, exp %s, got %s", tc.comment, t, s.snapDeclAssertsTime)) 926 return nil 927 }) 928 cleanups = append(cleanups, restore) 929 930 mnts := []systemdMount{ 931 ubuntuLabelMount("ubuntu-boot", "run"), 932 ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"), 933 ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"), 934 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 935 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 936 } 937 938 if isFirstBoot { 939 mnts = append(mnts, s.makeSeedSnapSystemdMount(snap.TypeSnapd)) 940 } 941 942 restore = s.mockSystemdMountSequence(c, mnts, nil) 943 cleanups = append(cleanups, restore) 944 945 // mock a bootloader 946 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 947 bootloader.Force(bloader) 948 cleanups = append(cleanups, func() { bootloader.Force(nil) }) 949 950 // set the current kernel 951 restore = bloader.SetEnabledKernel(s.kernel) 952 cleanups = append(cleanups, restore) 953 954 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 955 956 // write modeenv 957 modeEnv := boot.Modeenv{ 958 Mode: "run", 959 Base: s.core20.Filename(), 960 CurrentKernels: []string{s.kernel.Filename()}, 961 } 962 963 if isFirstBoot { 964 // set RecoverySystem so that the system operates in first boot 965 // of run mode, and still reads the system essential snaps to 966 // mount the snapd snap 967 modeEnv.RecoverySystem = "20191118" 968 } 969 970 err = modeEnv.WriteTo(boot.InitramfsWritableDir) 971 c.Assert(err, IsNil, comment) 972 973 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 974 c.Assert(err, IsNil, comment) 975 976 if isFirstBoot { 977 c.Assert(osutilSetTimeCalls, Equals, tc.setTimeCalls, comment) 978 } else { 979 // non-first boot should not have moved the time at all since it 980 // doesn't read assertions 981 c.Assert(osutilSetTimeCalls, Equals, 0, comment) 982 } 983 984 for _, r := range cleanups { 985 r() 986 } 987 } 988 989 } 990 } 991 992 func (s *initramfsMountsSuite) testInitramfsMountsRunModeNoSaveUnencrypted(c *C) error { 993 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 994 995 restore := disks.MockMountPointDisksToPartitionMapping( 996 map[disks.Mountpoint]*disks.MockDiskMapping{ 997 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootDisk, 998 {Mountpoint: boot.InitramfsDataDir}: defaultBootDisk, 999 }, 1000 ) 1001 defer restore() 1002 1003 restore = s.mockSystemdMountSequence(c, []systemdMount{ 1004 ubuntuLabelMount("ubuntu-boot", "run"), 1005 ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"), 1006 ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"), 1007 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 1008 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 1009 }, nil) 1010 defer restore() 1011 1012 // mock a bootloader 1013 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 1014 bootloader.Force(bloader) 1015 defer bootloader.Force(nil) 1016 1017 // set the current kernel 1018 restore = bloader.SetEnabledKernel(s.kernel) 1019 defer restore() 1020 1021 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 1022 1023 // write modeenv 1024 modeEnv := boot.Modeenv{ 1025 Mode: "run", 1026 Base: s.core20.Filename(), 1027 CurrentKernels: []string{s.kernel.Filename()}, 1028 } 1029 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 1030 c.Assert(err, IsNil) 1031 1032 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 1033 return err 1034 } 1035 1036 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeNoSaveUnencryptedHappy(c *C) { 1037 // ensure that we check that access to sealed keys were locked 1038 sealedKeysLocked := false 1039 defer main.MockSecbootLockSealedKeys(func() error { 1040 sealedKeysLocked = true 1041 return nil 1042 })() 1043 1044 err := s.testInitramfsMountsRunModeNoSaveUnencrypted(c) 1045 c.Assert(err, IsNil) 1046 1047 c.Check(sealedKeysLocked, Equals, true) 1048 } 1049 1050 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeNoSaveUnencryptedKeyLockingUnhappy(c *C) { 1051 // have blocking sealed keys fail 1052 defer main.MockSecbootLockSealedKeys(func() error { 1053 return fmt.Errorf("blocking keys failed") 1054 })() 1055 1056 err := s.testInitramfsMountsRunModeNoSaveUnencrypted(c) 1057 c.Assert(err, ErrorMatches, "error locking access to sealed keys: blocking keys failed") 1058 } 1059 1060 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeRealSystemdMountTimesOutNoMount(c *C) { 1061 s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel) 1062 1063 testStart := time.Now() 1064 timeCalls := 0 1065 restore := main.MockTimeNow(func() time.Time { 1066 timeCalls++ 1067 switch timeCalls { 1068 case 1, 2: 1069 return testStart 1070 case 3: 1071 // 1:31 later, we should time out 1072 return testStart.Add(1*time.Minute + 31*time.Second) 1073 default: 1074 c.Errorf("unexpected time.Now() call (%d)", timeCalls) 1075 // we want the test to fail at some point and not run forever, so 1076 // move time way forward to make it for sure time out 1077 return testStart.Add(10000 * time.Hour) 1078 } 1079 }) 1080 defer restore() 1081 1082 cmd := testutil.MockCommand(c, "systemd-mount", ``) 1083 defer cmd.Restore() 1084 1085 isMountedCalls := 0 1086 restore = main.MockOsutilIsMounted(func(where string) (bool, error) { 1087 isMountedCalls++ 1088 switch isMountedCalls { 1089 // always return false for the mount 1090 case 1, 2: 1091 c.Assert(where, Equals, boot.InitramfsUbuntuSeedDir) 1092 return false, nil 1093 default: 1094 // shouldn't be called more than twice due to the time.Now() mocking 1095 c.Errorf("test broken, IsMounted called too many (%d) times", isMountedCalls) 1096 return false, fmt.Errorf("test broken, IsMounted called too many (%d) times", isMountedCalls) 1097 } 1098 }) 1099 defer restore() 1100 1101 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 1102 c.Assert(err, ErrorMatches, fmt.Sprintf("timed out after 1m30s waiting for mount %s on %s", "/dev/disk/by-label/ubuntu-seed", boot.InitramfsUbuntuSeedDir)) 1103 c.Check(s.Stdout.String(), Equals, "") 1104 1105 } 1106 1107 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeHappyRealSystemdMount(c *C) { 1108 s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel) 1109 1110 baseMnt := filepath.Join(boot.InitramfsRunMntDir, "base") 1111 kernelMnt := filepath.Join(boot.InitramfsRunMntDir, "kernel") 1112 snapdMnt := filepath.Join(boot.InitramfsRunMntDir, "snapd") 1113 1114 // don't do anything from systemd-mount, we verify the arguments passed at 1115 // the end with cmd.Calls 1116 cmd := testutil.MockCommand(c, "systemd-mount", ``) 1117 defer cmd.Restore() 1118 1119 // mock that in turn, /run/mnt/ubuntu-boot, /run/mnt/ubuntu-seed, etc. are 1120 // mounted 1121 n := 0 1122 restore := main.MockOsutilIsMounted(func(where string) (bool, error) { 1123 n++ 1124 switch n { 1125 // first call for each mount returns false, then returns true, this 1126 // tests in the case where systemd is racy / inconsistent and things 1127 // aren't mounted by the time systemd-mount returns 1128 case 1, 2: 1129 c.Assert(where, Equals, boot.InitramfsUbuntuSeedDir) 1130 return n%2 == 0, nil 1131 case 3, 4: 1132 c.Assert(where, Equals, snapdMnt) 1133 return n%2 == 0, nil 1134 case 5, 6: 1135 c.Assert(where, Equals, kernelMnt) 1136 return n%2 == 0, nil 1137 case 7, 8: 1138 c.Assert(where, Equals, baseMnt) 1139 return n%2 == 0, nil 1140 case 9, 10: 1141 c.Assert(where, Equals, boot.InitramfsDataDir) 1142 return n%2 == 0, nil 1143 default: 1144 c.Errorf("unexpected IsMounted check on %s", where) 1145 return false, fmt.Errorf("unexpected IsMounted check on %s", where) 1146 } 1147 }) 1148 defer restore() 1149 1150 // mock a bootloader 1151 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 1152 bootloader.Force(bloader) 1153 defer bootloader.Force(nil) 1154 1155 // set the current kernel 1156 restore = bloader.SetEnabledKernel(s.kernel) 1157 defer restore() 1158 1159 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 1160 1161 // write modeenv 1162 modeEnv := boot.Modeenv{ 1163 Mode: "run", 1164 Base: s.core20.Filename(), 1165 CurrentKernels: []string{s.kernel.Filename()}, 1166 } 1167 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 1168 c.Assert(err, IsNil) 1169 1170 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 1171 c.Assert(err, IsNil) 1172 c.Check(s.Stdout.String(), Equals, "") 1173 1174 // check that all of the override files are present 1175 for _, initrdUnit := range []string{ 1176 "initrd.target", 1177 "initrd-fs.target", 1178 "initrd-switch-root.target", 1179 "local-fs.target", 1180 } { 1181 for _, mountUnit := range []string{ 1182 systemd.EscapeUnitNamePath(boot.InitramfsUbuntuSeedDir), 1183 systemd.EscapeUnitNamePath(snapdMnt), 1184 systemd.EscapeUnitNamePath(kernelMnt), 1185 systemd.EscapeUnitNamePath(baseMnt), 1186 systemd.EscapeUnitNamePath(boot.InitramfsDataDir), 1187 } { 1188 fname := fmt.Sprintf("snap_bootstrap_%s.conf", mountUnit) 1189 unitFile := filepath.Join(dirs.GlobalRootDir, "/run/systemd/system", initrdUnit+".d", fname) 1190 c.Assert(unitFile, testutil.FileEquals, fmt.Sprintf(`[Unit] 1191 Requires=%[1]s 1192 After=%[1]s 1193 `, mountUnit+".mount")) 1194 } 1195 } 1196 1197 // 2 IsMounted calls per mount point, so 10 total IsMounted calls 1198 c.Assert(n, Equals, 10) 1199 1200 c.Assert(cmd.Calls(), DeepEquals, [][]string{ 1201 { 1202 "systemd-mount", 1203 "/dev/disk/by-label/ubuntu-seed", 1204 boot.InitramfsUbuntuSeedDir, 1205 "--no-pager", 1206 "--no-ask-password", 1207 "--fsck=yes", 1208 }, { 1209 "systemd-mount", 1210 filepath.Join(s.seedDir, "snaps", s.snapd.Filename()), 1211 snapdMnt, 1212 "--no-pager", 1213 "--no-ask-password", 1214 "--fsck=no", 1215 }, { 1216 "systemd-mount", 1217 filepath.Join(s.seedDir, "snaps", s.kernel.Filename()), 1218 kernelMnt, 1219 "--no-pager", 1220 "--no-ask-password", 1221 "--fsck=no", 1222 }, { 1223 "systemd-mount", 1224 filepath.Join(s.seedDir, "snaps", s.core20.Filename()), 1225 baseMnt, 1226 "--no-pager", 1227 "--no-ask-password", 1228 "--fsck=no", 1229 }, { 1230 "systemd-mount", 1231 "tmpfs", 1232 boot.InitramfsDataDir, 1233 "--no-pager", 1234 "--no-ask-password", 1235 "--type=tmpfs", 1236 "--fsck=no", 1237 "--options=nosuid", 1238 }, 1239 }) 1240 } 1241 1242 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeNoSaveHappyRealSystemdMount(c *C) { 1243 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 1244 1245 baseMnt := filepath.Join(boot.InitramfsRunMntDir, "base") 1246 kernelMnt := filepath.Join(boot.InitramfsRunMntDir, "kernel") 1247 snapdMnt := filepath.Join(boot.InitramfsRunMntDir, "snapd") 1248 1249 restore := disks.MockMountPointDisksToPartitionMapping( 1250 map[disks.Mountpoint]*disks.MockDiskMapping{ 1251 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultBootDisk, 1252 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootDisk, 1253 {Mountpoint: boot.InitramfsHostUbuntuDataDir}: defaultBootDisk, 1254 }, 1255 ) 1256 defer restore() 1257 1258 // don't do anything from systemd-mount, we verify the arguments passed at 1259 // the end with cmd.Calls 1260 cmd := testutil.MockCommand(c, "systemd-mount", ``) 1261 defer cmd.Restore() 1262 1263 // mock that in turn, /run/mnt/ubuntu-boot, /run/mnt/ubuntu-seed, etc. are 1264 // mounted 1265 n := 0 1266 restore = main.MockOsutilIsMounted(func(where string) (bool, error) { 1267 n++ 1268 switch n { 1269 // first call for each mount returns false, then returns true, this 1270 // tests in the case where systemd is racy / inconsistent and things 1271 // aren't mounted by the time systemd-mount returns 1272 case 1, 2: 1273 c.Assert(where, Equals, boot.InitramfsUbuntuSeedDir) 1274 return n%2 == 0, nil 1275 case 3, 4: 1276 c.Assert(where, Equals, snapdMnt) 1277 return n%2 == 0, nil 1278 case 5, 6: 1279 c.Assert(where, Equals, kernelMnt) 1280 return n%2 == 0, nil 1281 case 7, 8: 1282 c.Assert(where, Equals, baseMnt) 1283 return n%2 == 0, nil 1284 case 9, 10: 1285 c.Assert(where, Equals, boot.InitramfsDataDir) 1286 return n%2 == 0, nil 1287 case 11, 12: 1288 c.Assert(where, Equals, boot.InitramfsUbuntuBootDir) 1289 return n%2 == 0, nil 1290 case 13, 14: 1291 c.Assert(where, Equals, boot.InitramfsHostUbuntuDataDir) 1292 return n%2 == 0, nil 1293 default: 1294 c.Errorf("unexpected IsMounted check on %s", where) 1295 return false, fmt.Errorf("unexpected IsMounted check on %s", where) 1296 } 1297 }) 1298 defer restore() 1299 1300 unlockVolumeWithSealedKeyCalls := 0 1301 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 1302 // this test doesn't use ubuntu-save, so we need to return an 1303 // unencrypted ubuntu-data the first time, but not found the second time 1304 unlockVolumeWithSealedKeyCalls++ 1305 switch unlockVolumeWithSealedKeyCalls { 1306 case 1: 1307 return foundUnencrypted(name), nil 1308 case 2: 1309 return notFoundPart(), fmt.Errorf("error enumerating to find ubuntu-save") 1310 default: 1311 c.Errorf("unexpected call (number %d) to UnlockVolumeUsingSealedKeyIfEncrypted", unlockVolumeWithSealedKeyCalls) 1312 return secboot.UnlockResult{}, fmt.Errorf("unexpected call (%d) to UnlockVolumeUsingSealedKeyIfEncrypted", unlockVolumeWithSealedKeyCalls) 1313 } 1314 }) 1315 defer restore() 1316 1317 // mock a bootloader 1318 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 1319 bootloader.Force(bloader) 1320 defer bootloader.Force(nil) 1321 1322 // set the current kernel 1323 restore = bloader.SetEnabledKernel(s.kernel) 1324 defer restore() 1325 1326 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 1327 1328 // write modeenv 1329 modeEnv := boot.Modeenv{ 1330 Mode: "run", 1331 Base: s.core20.Filename(), 1332 CurrentKernels: []string{s.kernel.Filename()}, 1333 } 1334 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 1335 c.Assert(err, IsNil) 1336 1337 s.testRecoverModeHappy(c) 1338 1339 c.Check(s.Stdout.String(), Equals, "") 1340 1341 // check that all of the override files are present 1342 for _, initrdUnit := range []string{ 1343 "initrd.target", 1344 "initrd-fs.target", 1345 "initrd-switch-root.target", 1346 "local-fs.target", 1347 } { 1348 for _, mountUnit := range []string{ 1349 systemd.EscapeUnitNamePath(boot.InitramfsUbuntuSeedDir), 1350 systemd.EscapeUnitNamePath(snapdMnt), 1351 systemd.EscapeUnitNamePath(kernelMnt), 1352 systemd.EscapeUnitNamePath(baseMnt), 1353 systemd.EscapeUnitNamePath(boot.InitramfsDataDir), 1354 systemd.EscapeUnitNamePath(boot.InitramfsHostUbuntuDataDir), 1355 } { 1356 fname := fmt.Sprintf("snap_bootstrap_%s.conf", mountUnit) 1357 unitFile := filepath.Join(dirs.GlobalRootDir, "/run/systemd/system", initrdUnit+".d", fname) 1358 c.Assert(unitFile, testutil.FileEquals, fmt.Sprintf(`[Unit] 1359 Requires=%[1]s 1360 After=%[1]s 1361 `, mountUnit+".mount")) 1362 } 1363 } 1364 1365 // 2 IsMounted calls per mount point, so 14 total IsMounted calls 1366 c.Assert(n, Equals, 14) 1367 1368 c.Assert(cmd.Calls(), DeepEquals, [][]string{ 1369 { 1370 "systemd-mount", 1371 "/dev/disk/by-label/ubuntu-seed", 1372 boot.InitramfsUbuntuSeedDir, 1373 "--no-pager", 1374 "--no-ask-password", 1375 "--fsck=yes", 1376 }, { 1377 "systemd-mount", 1378 filepath.Join(s.seedDir, "snaps", s.snapd.Filename()), 1379 snapdMnt, 1380 "--no-pager", 1381 "--no-ask-password", 1382 "--fsck=no", 1383 }, { 1384 "systemd-mount", 1385 filepath.Join(s.seedDir, "snaps", s.kernel.Filename()), 1386 kernelMnt, 1387 "--no-pager", 1388 "--no-ask-password", 1389 "--fsck=no", 1390 }, { 1391 "systemd-mount", 1392 filepath.Join(s.seedDir, "snaps", s.core20.Filename()), 1393 baseMnt, 1394 "--no-pager", 1395 "--no-ask-password", 1396 "--fsck=no", 1397 }, { 1398 "systemd-mount", 1399 "tmpfs", 1400 boot.InitramfsDataDir, 1401 "--no-pager", 1402 "--no-ask-password", 1403 "--type=tmpfs", 1404 "--fsck=no", 1405 "--options=nosuid", 1406 }, { 1407 "systemd-mount", 1408 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 1409 boot.InitramfsUbuntuBootDir, 1410 "--no-pager", 1411 "--no-ask-password", 1412 "--fsck=yes", 1413 }, { 1414 "systemd-mount", 1415 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 1416 boot.InitramfsHostUbuntuDataDir, 1417 "--no-pager", 1418 "--no-ask-password", 1419 "--fsck=no", 1420 "--options=nosuid", 1421 }, 1422 }) 1423 1424 // we should not have written a degraded.json 1425 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "degraded.json"), testutil.FileAbsent) 1426 1427 // we should have only tried to unseal things only once, when unlocking ubuntu-data 1428 c.Assert(unlockVolumeWithSealedKeyCalls, Equals, 1) 1429 1430 // save is optional and not found in this test 1431 c.Check(s.logs.String(), testutil.Contains, "ubuntu-save was not found") 1432 } 1433 1434 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeWithSaveHappyRealSystemdMount(c *C) { 1435 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 1436 1437 restore := disks.MockMountPointDisksToPartitionMapping( 1438 map[disks.Mountpoint]*disks.MockDiskMapping{ 1439 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultBootWithSaveDisk, 1440 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootWithSaveDisk, 1441 {Mountpoint: boot.InitramfsHostUbuntuDataDir}: defaultBootWithSaveDisk, 1442 {Mountpoint: boot.InitramfsUbuntuSaveDir}: defaultBootWithSaveDisk, 1443 }, 1444 ) 1445 defer restore() 1446 1447 baseMnt := filepath.Join(boot.InitramfsRunMntDir, "base") 1448 kernelMnt := filepath.Join(boot.InitramfsRunMntDir, "kernel") 1449 snapdMnt := filepath.Join(boot.InitramfsRunMntDir, "snapd") 1450 1451 // don't do anything from systemd-mount, we verify the arguments passed at 1452 // the end with cmd.Calls 1453 cmd := testutil.MockCommand(c, "systemd-mount", ``) 1454 defer cmd.Restore() 1455 1456 isMountedChecks := []string{} 1457 restore = main.MockOsutilIsMounted(func(where string) (bool, error) { 1458 isMountedChecks = append(isMountedChecks, where) 1459 return true, nil 1460 }) 1461 defer restore() 1462 1463 // mock a bootloader 1464 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 1465 bootloader.Force(bloader) 1466 defer bootloader.Force(nil) 1467 1468 // set the current kernel 1469 restore = bloader.SetEnabledKernel(s.kernel) 1470 defer restore() 1471 1472 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 1473 1474 // write modeenv 1475 modeEnv := boot.Modeenv{ 1476 Mode: "run", 1477 Base: s.core20.Filename(), 1478 CurrentKernels: []string{s.kernel.Filename()}, 1479 } 1480 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 1481 c.Assert(err, IsNil) 1482 1483 s.testRecoverModeHappy(c) 1484 1485 c.Check(s.Stdout.String(), Equals, "") 1486 1487 // check that all of the override files are present 1488 for _, initrdUnit := range []string{ 1489 "initrd.target", 1490 "initrd-fs.target", 1491 "initrd-switch-root.target", 1492 "local-fs.target", 1493 } { 1494 1495 mountUnit := systemd.EscapeUnitNamePath(boot.InitramfsUbuntuSaveDir) 1496 fname := fmt.Sprintf("snap_bootstrap_%s.conf", mountUnit) 1497 unitFile := filepath.Join(dirs.GlobalRootDir, "/run/systemd/system", initrdUnit+".d", fname) 1498 c.Assert(unitFile, testutil.FileEquals, fmt.Sprintf(`[Unit] 1499 Requires=%[1]s 1500 After=%[1]s 1501 `, mountUnit+".mount")) 1502 } 1503 1504 c.Check(isMountedChecks, DeepEquals, []string{ 1505 boot.InitramfsUbuntuSeedDir, 1506 snapdMnt, 1507 kernelMnt, 1508 baseMnt, 1509 boot.InitramfsDataDir, 1510 boot.InitramfsUbuntuBootDir, 1511 boot.InitramfsHostUbuntuDataDir, 1512 boot.InitramfsUbuntuSaveDir, 1513 }) 1514 c.Check(cmd.Calls(), DeepEquals, [][]string{ 1515 { 1516 "systemd-mount", 1517 "/dev/disk/by-label/ubuntu-seed", 1518 boot.InitramfsUbuntuSeedDir, 1519 "--no-pager", 1520 "--no-ask-password", 1521 "--fsck=yes", 1522 }, { 1523 "systemd-mount", 1524 filepath.Join(s.seedDir, "snaps", s.snapd.Filename()), 1525 snapdMnt, 1526 "--no-pager", 1527 "--no-ask-password", 1528 "--fsck=no", 1529 }, { 1530 "systemd-mount", 1531 filepath.Join(s.seedDir, "snaps", s.kernel.Filename()), 1532 kernelMnt, 1533 "--no-pager", 1534 "--no-ask-password", 1535 "--fsck=no", 1536 }, { 1537 "systemd-mount", 1538 filepath.Join(s.seedDir, "snaps", s.core20.Filename()), 1539 baseMnt, 1540 "--no-pager", 1541 "--no-ask-password", 1542 "--fsck=no", 1543 }, { 1544 "systemd-mount", 1545 "tmpfs", 1546 boot.InitramfsDataDir, 1547 "--no-pager", 1548 "--no-ask-password", 1549 "--type=tmpfs", 1550 "--fsck=no", 1551 "--options=nosuid", 1552 }, { 1553 "systemd-mount", 1554 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 1555 boot.InitramfsUbuntuBootDir, 1556 "--no-pager", 1557 "--no-ask-password", 1558 "--fsck=yes", 1559 }, { 1560 "systemd-mount", 1561 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 1562 boot.InitramfsHostUbuntuDataDir, 1563 "--no-pager", 1564 "--no-ask-password", 1565 "--fsck=no", 1566 "--options=nosuid", 1567 }, { 1568 "systemd-mount", 1569 "/dev/disk/by-partuuid/ubuntu-save-partuuid", 1570 boot.InitramfsUbuntuSaveDir, 1571 "--no-pager", 1572 "--no-ask-password", 1573 "--fsck=no", 1574 }, 1575 }) 1576 1577 // we should not have written a degraded.json 1578 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "degraded.json"), testutil.FileAbsent) 1579 1580 // save is optional and found in this test 1581 c.Check(s.logs.String(), Not(testutil.Contains), "ubuntu-save was not found") 1582 } 1583 1584 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeHappyNoSaveRealSystemdMount(c *C) { 1585 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 1586 1587 restore := disks.MockMountPointDisksToPartitionMapping( 1588 map[disks.Mountpoint]*disks.MockDiskMapping{ 1589 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootDisk, 1590 {Mountpoint: boot.InitramfsDataDir}: defaultBootDisk, 1591 }, 1592 ) 1593 defer restore() 1594 1595 baseMnt := filepath.Join(boot.InitramfsRunMntDir, "base") 1596 kernelMnt := filepath.Join(boot.InitramfsRunMntDir, "kernel") 1597 1598 // don't do anything from systemd-mount, we verify the arguments passed at 1599 // the end with cmd.Calls 1600 cmd := testutil.MockCommand(c, "systemd-mount", ``) 1601 defer cmd.Restore() 1602 1603 // mock that in turn, /run/mnt/ubuntu-boot, /run/mnt/ubuntu-seed, etc. are 1604 // mounted 1605 n := 0 1606 restore = main.MockOsutilIsMounted(func(where string) (bool, error) { 1607 n++ 1608 switch n { 1609 // first call for each mount returns false, then returns true, this 1610 // tests in the case where systemd is racy / inconsistent and things 1611 // aren't mounted by the time systemd-mount returns 1612 case 1, 2: 1613 c.Assert(where, Equals, boot.InitramfsUbuntuBootDir) 1614 return n%2 == 0, nil 1615 case 3, 4: 1616 c.Assert(where, Equals, boot.InitramfsUbuntuSeedDir) 1617 return n%2 == 0, nil 1618 case 5, 6: 1619 c.Assert(where, Equals, boot.InitramfsDataDir) 1620 return n%2 == 0, nil 1621 case 7, 8: 1622 c.Assert(where, Equals, baseMnt) 1623 return n%2 == 0, nil 1624 case 9, 10: 1625 c.Assert(where, Equals, kernelMnt) 1626 return n%2 == 0, nil 1627 default: 1628 c.Errorf("unexpected IsMounted check on %s", where) 1629 return false, fmt.Errorf("unexpected IsMounted check on %s", where) 1630 } 1631 }) 1632 defer restore() 1633 1634 // mock a bootloader 1635 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 1636 bootloader.Force(bloader) 1637 defer bootloader.Force(nil) 1638 1639 // set the current kernel 1640 restore = bloader.SetEnabledKernel(s.kernel) 1641 defer restore() 1642 1643 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 1644 1645 // write modeenv 1646 modeEnv := boot.Modeenv{ 1647 Mode: "run", 1648 Base: s.core20.Filename(), 1649 CurrentKernels: []string{s.kernel.Filename()}, 1650 } 1651 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 1652 c.Assert(err, IsNil) 1653 1654 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 1655 c.Assert(err, IsNil) 1656 c.Check(s.Stdout.String(), Equals, "") 1657 1658 // check that all of the override files are present 1659 for _, initrdUnit := range []string{ 1660 "initrd.target", 1661 "initrd-fs.target", 1662 "initrd-switch-root.target", 1663 "local-fs.target", 1664 } { 1665 for _, mountUnit := range []string{ 1666 systemd.EscapeUnitNamePath(boot.InitramfsUbuntuBootDir), 1667 systemd.EscapeUnitNamePath(boot.InitramfsUbuntuSeedDir), 1668 systemd.EscapeUnitNamePath(boot.InitramfsDataDir), 1669 systemd.EscapeUnitNamePath(baseMnt), 1670 systemd.EscapeUnitNamePath(kernelMnt), 1671 } { 1672 fname := fmt.Sprintf("snap_bootstrap_%s.conf", mountUnit) 1673 unitFile := filepath.Join(dirs.GlobalRootDir, "/run/systemd/system", initrdUnit+".d", fname) 1674 c.Assert(unitFile, testutil.FileEquals, fmt.Sprintf(`[Unit] 1675 Requires=%[1]s 1676 After=%[1]s 1677 `, mountUnit+".mount")) 1678 } 1679 } 1680 1681 // 2 IsMounted calls per mount point, so 10 total IsMounted calls 1682 c.Assert(n, Equals, 10) 1683 1684 c.Assert(cmd.Calls(), DeepEquals, [][]string{ 1685 { 1686 "systemd-mount", 1687 "/dev/disk/by-label/ubuntu-boot", 1688 boot.InitramfsUbuntuBootDir, 1689 "--no-pager", 1690 "--no-ask-password", 1691 "--fsck=yes", 1692 }, { 1693 "systemd-mount", 1694 "/dev/disk/by-partuuid/ubuntu-seed-partuuid", 1695 boot.InitramfsUbuntuSeedDir, 1696 "--no-pager", 1697 "--no-ask-password", 1698 "--fsck=yes", 1699 }, { 1700 "systemd-mount", 1701 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 1702 boot.InitramfsDataDir, 1703 "--no-pager", 1704 "--no-ask-password", 1705 "--fsck=yes", 1706 "--options=nosuid", 1707 }, { 1708 "systemd-mount", 1709 filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), s.core20.Filename()), 1710 baseMnt, 1711 "--no-pager", 1712 "--no-ask-password", 1713 "--fsck=no", 1714 }, { 1715 "systemd-mount", 1716 filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), s.kernel.Filename()), 1717 kernelMnt, 1718 "--no-pager", 1719 "--no-ask-password", 1720 "--fsck=no", 1721 }, 1722 }) 1723 } 1724 1725 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeWithSaveHappyRealSystemdMount(c *C) { 1726 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 1727 1728 restore := disks.MockMountPointDisksToPartitionMapping( 1729 map[disks.Mountpoint]*disks.MockDiskMapping{ 1730 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootWithSaveDisk, 1731 {Mountpoint: boot.InitramfsDataDir}: defaultBootWithSaveDisk, 1732 {Mountpoint: boot.InitramfsUbuntuSaveDir}: defaultBootWithSaveDisk, 1733 }, 1734 ) 1735 defer restore() 1736 1737 baseMnt := filepath.Join(boot.InitramfsRunMntDir, "base") 1738 kernelMnt := filepath.Join(boot.InitramfsRunMntDir, "kernel") 1739 1740 // don't do anything from systemd-mount, we verify the arguments passed at 1741 // the end with cmd.Calls 1742 cmd := testutil.MockCommand(c, "systemd-mount", ``) 1743 defer cmd.Restore() 1744 1745 isMountedChecks := []string{} 1746 restore = main.MockOsutilIsMounted(func(where string) (bool, error) { 1747 isMountedChecks = append(isMountedChecks, where) 1748 return true, nil 1749 }) 1750 defer restore() 1751 1752 // mock a bootloader 1753 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 1754 bootloader.Force(bloader) 1755 defer bootloader.Force(nil) 1756 1757 // set the current kernel 1758 restore = bloader.SetEnabledKernel(s.kernel) 1759 defer restore() 1760 1761 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 1762 1763 // write modeenv 1764 modeEnv := boot.Modeenv{ 1765 Mode: "run", 1766 Base: s.core20.Filename(), 1767 CurrentKernels: []string{s.kernel.Filename()}, 1768 } 1769 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 1770 c.Assert(err, IsNil) 1771 1772 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 1773 c.Assert(err, IsNil) 1774 c.Check(s.Stdout.String(), Equals, "") 1775 1776 // check that all of the override files are present 1777 for _, initrdUnit := range []string{ 1778 "initrd.target", 1779 "initrd-fs.target", 1780 "initrd-switch-root.target", 1781 "local-fs.target", 1782 } { 1783 1784 mountUnit := systemd.EscapeUnitNamePath(boot.InitramfsUbuntuSaveDir) 1785 fname := fmt.Sprintf("snap_bootstrap_%s.conf", mountUnit) 1786 unitFile := filepath.Join(dirs.GlobalRootDir, "/run/systemd/system", initrdUnit+".d", fname) 1787 c.Assert(unitFile, testutil.FileEquals, fmt.Sprintf(`[Unit] 1788 Requires=%[1]s 1789 After=%[1]s 1790 `, mountUnit+".mount")) 1791 } 1792 1793 c.Check(isMountedChecks, DeepEquals, []string{ 1794 boot.InitramfsUbuntuBootDir, 1795 boot.InitramfsUbuntuSeedDir, 1796 boot.InitramfsDataDir, 1797 boot.InitramfsUbuntuSaveDir, 1798 baseMnt, 1799 kernelMnt, 1800 }) 1801 c.Check(cmd.Calls(), DeepEquals, [][]string{ 1802 { 1803 "systemd-mount", 1804 "/dev/disk/by-label/ubuntu-boot", 1805 boot.InitramfsUbuntuBootDir, 1806 "--no-pager", 1807 "--no-ask-password", 1808 "--fsck=yes", 1809 }, { 1810 "systemd-mount", 1811 "/dev/disk/by-partuuid/ubuntu-seed-partuuid", 1812 boot.InitramfsUbuntuSeedDir, 1813 "--no-pager", 1814 "--no-ask-password", 1815 "--fsck=yes", 1816 }, { 1817 "systemd-mount", 1818 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 1819 boot.InitramfsDataDir, 1820 "--no-pager", 1821 "--no-ask-password", 1822 "--fsck=yes", 1823 "--options=nosuid", 1824 }, { 1825 "systemd-mount", 1826 "/dev/disk/by-partuuid/ubuntu-save-partuuid", 1827 boot.InitramfsUbuntuSaveDir, 1828 "--no-pager", 1829 "--no-ask-password", 1830 "--fsck=yes", 1831 }, { 1832 "systemd-mount", 1833 filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), s.core20.Filename()), 1834 baseMnt, 1835 "--no-pager", 1836 "--no-ask-password", 1837 "--fsck=no", 1838 }, { 1839 "systemd-mount", 1840 filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), s.kernel.Filename()), 1841 kernelMnt, 1842 "--no-pager", 1843 "--no-ask-password", 1844 "--fsck=no", 1845 }, 1846 }) 1847 } 1848 1849 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeFirstBootRecoverySystemSetHappy(c *C) { 1850 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 1851 1852 restore := disks.MockMountPointDisksToPartitionMapping( 1853 map[disks.Mountpoint]*disks.MockDiskMapping{ 1854 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootWithSaveDisk, 1855 {Mountpoint: boot.InitramfsDataDir}: defaultBootWithSaveDisk, 1856 {Mountpoint: boot.InitramfsUbuntuSaveDir}: defaultBootWithSaveDisk, 1857 }, 1858 ) 1859 defer restore() 1860 1861 restore = s.mockSystemdMountSequence(c, []systemdMount{ 1862 ubuntuLabelMount("ubuntu-boot", "run"), 1863 ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"), 1864 ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"), 1865 ubuntuPartUUIDMount("ubuntu-save-partuuid", "run"), 1866 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 1867 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 1868 // RecoverySystem set makes us mount the snapd snap here 1869 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 1870 }, nil) 1871 defer restore() 1872 1873 // mock a bootloader 1874 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 1875 bootloader.Force(bloader) 1876 defer bootloader.Force(nil) 1877 1878 // set the current kernel 1879 restore = bloader.SetEnabledKernel(s.kernel) 1880 defer restore() 1881 1882 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 1883 1884 // write modeenv 1885 modeEnv := boot.Modeenv{ 1886 Mode: "run", 1887 RecoverySystem: "20191118", 1888 Base: s.core20.Filename(), 1889 CurrentKernels: []string{s.kernel.Filename()}, 1890 } 1891 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 1892 c.Assert(err, IsNil) 1893 1894 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 1895 c.Assert(err, IsNil) 1896 1897 // we should not have written a degraded.json 1898 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "degraded.json"), testutil.FileAbsent) 1899 } 1900 1901 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeWithBootedKernelPartUUIDHappy(c *C) { 1902 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 1903 1904 restore := main.MockPartitionUUIDForBootedKernelDisk("ubuntu-boot-partuuid") 1905 defer restore() 1906 1907 restore = disks.MockMountPointDisksToPartitionMapping( 1908 map[disks.Mountpoint]*disks.MockDiskMapping{ 1909 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootDisk, 1910 {Mountpoint: boot.InitramfsDataDir}: defaultBootDisk, 1911 }, 1912 ) 1913 defer restore() 1914 1915 restore = s.mockSystemdMountSequence(c, []systemdMount{ 1916 { 1917 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 1918 boot.InitramfsUbuntuBootDir, 1919 needsFsckDiskMountOpts, 1920 }, 1921 ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"), 1922 ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"), 1923 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 1924 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 1925 }, nil) 1926 defer restore() 1927 1928 // mock a bootloader 1929 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 1930 bootloader.Force(bloader) 1931 defer bootloader.Force(nil) 1932 1933 // set the current kernel 1934 restore = bloader.SetEnabledKernel(s.kernel) 1935 defer restore() 1936 1937 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 1938 1939 // write modeenv 1940 modeEnv := boot.Modeenv{ 1941 Mode: "run", 1942 Base: s.core20.Filename(), 1943 CurrentKernels: []string{s.kernel.Filename()}, 1944 } 1945 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 1946 c.Assert(err, IsNil) 1947 1948 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 1949 c.Assert(err, IsNil) 1950 } 1951 1952 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeEncryptedDataHappy(c *C) { 1953 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 1954 1955 // ensure that we check that access to sealed keys were locked 1956 sealedKeysLocked := false 1957 defer main.MockSecbootLockSealedKeys(func() error { 1958 sealedKeysLocked = true 1959 return nil 1960 })() 1961 1962 restore := disks.MockMountPointDisksToPartitionMapping( 1963 map[disks.Mountpoint]*disks.MockDiskMapping{ 1964 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultEncBootDisk, 1965 {Mountpoint: boot.InitramfsDataDir, IsDecryptedDevice: true}: defaultEncBootDisk, 1966 {Mountpoint: boot.InitramfsUbuntuSaveDir, IsDecryptedDevice: true}: defaultEncBootDisk, 1967 }, 1968 ) 1969 defer restore() 1970 1971 restore = s.mockSystemdMountSequence(c, []systemdMount{ 1972 ubuntuLabelMount("ubuntu-boot", "run"), 1973 ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"), 1974 { 1975 "/dev/mapper/ubuntu-data-random", 1976 boot.InitramfsDataDir, 1977 needsFsckAndNoSuidDiskMountOpts, 1978 }, 1979 { 1980 "/dev/mapper/ubuntu-save-random", 1981 boot.InitramfsUbuntuSaveDir, 1982 needsFsckDiskMountOpts, 1983 }, 1984 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 1985 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 1986 }, nil) 1987 defer restore() 1988 1989 // write the installed model like makebootable does it 1990 err := os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "device"), 0755) 1991 c.Assert(err, IsNil) 1992 mf, err := os.Create(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model")) 1993 c.Assert(err, IsNil) 1994 defer mf.Close() 1995 err = asserts.NewEncoder(mf).Encode(s.model) 1996 c.Assert(err, IsNil) 1997 1998 dataActivated := false 1999 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 2000 c.Assert(name, Equals, "ubuntu-data") 2001 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")) 2002 c.Assert(opts.AllowRecoveryKey, Equals, true) 2003 c.Assert(opts.WhichModel, NotNil) 2004 mod, err := opts.WhichModel() 2005 c.Assert(err, IsNil) 2006 c.Check(mod.Model(), Equals, "my-model") 2007 2008 dataActivated = true 2009 // return true because we are using an encrypted device 2010 return happyUnlocked("ubuntu-data", secboot.UnlockedWithSealedKey), nil 2011 }) 2012 defer restore() 2013 2014 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsWritableDir, "foo", "marker") 2015 s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker") 2016 2017 saveActivated := false 2018 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 2019 c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not activated yet")) 2020 saveActivated = true 2021 c.Assert(name, Equals, "ubuntu-save") 2022 c.Assert(key, DeepEquals, []byte("foo")) 2023 return happyUnlocked("ubuntu-save", secboot.UnlockedWithKey), nil 2024 }) 2025 defer restore() 2026 2027 measureEpochCalls := 0 2028 measureModelCalls := 0 2029 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 2030 measureEpochCalls++ 2031 return nil 2032 }) 2033 defer restore() 2034 2035 var measuredModel *asserts.Model 2036 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 2037 measureModelCalls++ 2038 var err error 2039 measuredModel, err = findModel() 2040 if err != nil { 2041 return err 2042 } 2043 return nil 2044 }) 2045 defer restore() 2046 2047 // mock a bootloader 2048 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 2049 bootloader.Force(bloader) 2050 defer bootloader.Force(nil) 2051 2052 // set the current kernel 2053 restore = bloader.SetEnabledKernel(s.kernel) 2054 defer restore() 2055 2056 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 2057 2058 // write modeenv 2059 modeEnv := boot.Modeenv{ 2060 Mode: "run", 2061 Base: s.core20.Filename(), 2062 CurrentKernels: []string{s.kernel.Filename()}, 2063 } 2064 err = modeEnv.WriteTo(boot.InitramfsWritableDir) 2065 c.Assert(err, IsNil) 2066 2067 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 2068 c.Assert(err, IsNil) 2069 c.Check(dataActivated, Equals, true) 2070 c.Check(saveActivated, Equals, true) 2071 c.Check(measureEpochCalls, Equals, 1) 2072 c.Check(measureModelCalls, Equals, 1) 2073 c.Check(measuredModel, DeepEquals, s.model) 2074 c.Check(sealedKeysLocked, Equals, true) 2075 2076 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 2077 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "run-model-measured"), testutil.FilePresent) 2078 } 2079 2080 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeEncryptedDataUnhappyNoSave(c *C) { 2081 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 2082 2083 defaultEncNoSaveBootDisk := &disks.MockDiskMapping{ 2084 FilesystemLabelToPartUUID: map[string]string{ 2085 "ubuntu-boot": "ubuntu-boot-partuuid", 2086 "ubuntu-seed": "ubuntu-seed-partuuid", 2087 "ubuntu-data-enc": "ubuntu-data-enc-partuuid", 2088 // missing ubuntu-save 2089 }, 2090 DiskHasPartitions: true, 2091 DevNum: "defaultEncDev", 2092 } 2093 2094 restore := disks.MockMountPointDisksToPartitionMapping( 2095 map[disks.Mountpoint]*disks.MockDiskMapping{ 2096 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultEncNoSaveBootDisk, 2097 {Mountpoint: boot.InitramfsDataDir, IsDecryptedDevice: true}: defaultEncNoSaveBootDisk, 2098 }, 2099 ) 2100 defer restore() 2101 2102 restore = s.mockSystemdMountSequence(c, []systemdMount{ 2103 ubuntuLabelMount("ubuntu-boot", "run"), 2104 ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"), 2105 { 2106 "/dev/mapper/ubuntu-data-random", 2107 boot.InitramfsDataDir, 2108 needsFsckAndNoSuidDiskMountOpts, 2109 }, 2110 }, nil) 2111 defer restore() 2112 2113 dataActivated := false 2114 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 2115 c.Assert(name, Equals, "ubuntu-data") 2116 dataActivated = true 2117 // return true because we are using an encrypted device 2118 return happyUnlocked("ubuntu-data", secboot.UnlockedWithSealedKey), nil 2119 }) 2120 defer restore() 2121 2122 // the test does not mock ubuntu-save.key, the secboot helper for 2123 // opening a volume using the key should not be called 2124 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 2125 c.Fatal("unexpected call") 2126 return secboot.UnlockResult{}, fmt.Errorf("unexpected call") 2127 }) 2128 defer restore() 2129 2130 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { return nil }) 2131 defer restore() 2132 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 2133 return nil 2134 }) 2135 defer restore() 2136 2137 // mock a bootloader 2138 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 2139 bootloader.Force(bloader) 2140 defer bootloader.Force(nil) 2141 2142 // set the current kernel 2143 restore = bloader.SetEnabledKernel(s.kernel) 2144 defer restore() 2145 2146 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 2147 2148 // write modeenv 2149 modeEnv := boot.Modeenv{ 2150 Mode: "run", 2151 Base: s.core20.Filename(), 2152 CurrentKernels: []string{s.kernel.Filename()}, 2153 } 2154 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 2155 c.Assert(err, IsNil) 2156 2157 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 2158 c.Assert(err, ErrorMatches, "cannot find ubuntu-save encryption key at .*/run/mnt/data/system-data/var/lib/snapd/device/fde/ubuntu-save.key") 2159 c.Check(dataActivated, Equals, true) 2160 } 2161 2162 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeEncryptedDataUnhappyUnlockSaveFail(c *C) { 2163 // ensure that we check that access to sealed keys were locked 2164 sealedKeysLocked := false 2165 defer main.MockSecbootLockSealedKeys(func() error { 2166 sealedKeysLocked = true 2167 return fmt.Errorf("blocking keys failed") 2168 })() 2169 2170 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 2171 restore := disks.MockMountPointDisksToPartitionMapping( 2172 map[disks.Mountpoint]*disks.MockDiskMapping{ 2173 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultEncBootDisk, 2174 {Mountpoint: boot.InitramfsDataDir, IsDecryptedDevice: true}: defaultEncBootDisk, 2175 {Mountpoint: boot.InitramfsUbuntuSaveDir, IsDecryptedDevice: true}: defaultEncBootDisk, 2176 }, 2177 ) 2178 defer restore() 2179 2180 restore = s.mockSystemdMountSequence(c, []systemdMount{ 2181 ubuntuLabelMount("ubuntu-boot", "run"), 2182 ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"), 2183 { 2184 "/dev/mapper/ubuntu-data-random", 2185 boot.InitramfsDataDir, 2186 needsFsckAndNoSuidDiskMountOpts, 2187 }, 2188 }, nil) 2189 defer restore() 2190 2191 dataActivated := false 2192 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 2193 c.Assert(name, Equals, "ubuntu-data") 2194 dataActivated = true 2195 // return true because we are using an encrypted device 2196 return happyUnlocked("ubuntu-data", secboot.UnlockedWithSealedKey), nil 2197 }) 2198 defer restore() 2199 2200 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsWritableDir, "foo", "") 2201 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 2202 c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not yet activated")) 2203 return foundEncrypted("ubuntu-save"), fmt.Errorf("ubuntu-save unlock fail") 2204 }) 2205 defer restore() 2206 2207 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { return nil }) 2208 defer restore() 2209 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 2210 return nil 2211 }) 2212 defer restore() 2213 2214 // mock a bootloader 2215 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 2216 bootloader.Force(bloader) 2217 defer bootloader.Force(nil) 2218 2219 // set the current kernel 2220 restore = bloader.SetEnabledKernel(s.kernel) 2221 defer restore() 2222 2223 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 2224 2225 // write modeenv 2226 modeEnv := boot.Modeenv{ 2227 Mode: "run", 2228 Base: s.core20.Filename(), 2229 CurrentKernels: []string{s.kernel.Filename()}, 2230 } 2231 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 2232 c.Assert(err, IsNil) 2233 2234 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 2235 c.Assert(err, ErrorMatches, "cannot unlock ubuntu-save volume: ubuntu-save unlock fail") 2236 c.Check(dataActivated, Equals, true) 2237 // locking sealing keys was attempted, error was only logged 2238 c.Check(sealedKeysLocked, Equals, true) 2239 } 2240 2241 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeEncryptedNoModel(c *C) { 2242 s.testInitramfsMountsEncryptedNoModel(c, "run", "", 1) 2243 } 2244 2245 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeEncryptedNoModel(c *C) { 2246 s.testInitramfsMountsEncryptedNoModel(c, "install", s.sysLabel, 0) 2247 } 2248 2249 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedNoModel(c *C) { 2250 s.testInitramfsMountsEncryptedNoModel(c, "recover", s.sysLabel, 0) 2251 } 2252 2253 func (s *initramfsMountsSuite) testInitramfsMountsEncryptedNoModel(c *C, mode, label string, expectedMeasureModelCalls int) { 2254 s.mockProcCmdlineContent(c, fmt.Sprintf("snapd_recovery_mode=%s", mode)) 2255 2256 // ensure that we check that access to sealed keys were locked 2257 sealedKeysLocked := false 2258 defer main.MockSecbootLockSealedKeys(func() error { 2259 sealedKeysLocked = true 2260 return fmt.Errorf("blocking keys failed") 2261 })() 2262 2263 // install and recover mounts are just ubuntu-seed before we fail 2264 var restore func() 2265 if mode == "run" { 2266 // run mode will mount ubuntu-boot and ubuntu-seed 2267 restore = s.mockSystemdMountSequence(c, []systemdMount{ 2268 ubuntuLabelMount("ubuntu-boot", mode), 2269 ubuntuPartUUIDMount("ubuntu-seed-partuuid", mode), 2270 }, nil) 2271 restore2 := disks.MockMountPointDisksToPartitionMapping( 2272 map[disks.Mountpoint]*disks.MockDiskMapping{ 2273 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultEncBootDisk, 2274 }, 2275 ) 2276 defer restore2() 2277 } else { 2278 restore = s.mockSystemdMountSequence(c, []systemdMount{ 2279 ubuntuLabelMount("ubuntu-seed", mode), 2280 }, nil) 2281 2282 // in install / recover mode the code doesn't make it far enough to do 2283 // any disk cross checking 2284 } 2285 defer restore() 2286 2287 if label != "" { 2288 s.mockProcCmdlineContent(c, 2289 fmt.Sprintf("snapd_recovery_mode=%s snapd_recovery_system=%s", mode, label)) 2290 // break the seed 2291 err := os.Remove(filepath.Join(s.seedDir, "systems", label, "model")) 2292 c.Assert(err, IsNil) 2293 } 2294 2295 measureEpochCalls := 0 2296 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 2297 measureEpochCalls++ 2298 return nil 2299 }) 2300 defer restore() 2301 2302 measureModelCalls := 0 2303 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 2304 measureModelCalls++ 2305 _, err := findModel() 2306 if err != nil { 2307 return err 2308 } 2309 return fmt.Errorf("unexpected call") 2310 }) 2311 defer restore() 2312 2313 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 2314 where := "/run/mnt/ubuntu-boot/device/model" 2315 if mode != "run" { 2316 where = fmt.Sprintf("/run/mnt/ubuntu-seed/systems/%s/model", label) 2317 } 2318 c.Assert(err, ErrorMatches, fmt.Sprintf(".*cannot read model assertion: open .*%s: no such file or directory", where)) 2319 c.Assert(measureEpochCalls, Equals, 1) 2320 c.Assert(measureModelCalls, Equals, expectedMeasureModelCalls) 2321 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 2322 gl, err := filepath.Glob(filepath.Join(dirs.SnapBootstrapRunDir, "*-model-measured")) 2323 c.Assert(err, IsNil) 2324 c.Assert(gl, HasLen, 0) 2325 c.Check(sealedKeysLocked, Equals, true) 2326 } 2327 2328 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeUpgradeScenarios(c *C) { 2329 tt := []struct { 2330 modeenv *boot.Modeenv 2331 // this is a function so we can have delayed execution, typical values 2332 // depend on the root dir which changes for each test case 2333 additionalMountsFunc func() []systemdMount 2334 enableKernel snap.PlaceInfo 2335 enableTryKernel snap.PlaceInfo 2336 snapFiles []snap.PlaceInfo 2337 kernelStatus string 2338 2339 expRebootPanic string 2340 expLog string 2341 expError string 2342 expModeenv *boot.Modeenv 2343 comment string 2344 }{ 2345 // default case no upgrades 2346 { 2347 modeenv: &boot.Modeenv{ 2348 Mode: "run", 2349 Base: s.core20.Filename(), 2350 CurrentKernels: []string{s.kernel.Filename()}, 2351 }, 2352 additionalMountsFunc: func() []systemdMount { 2353 return []systemdMount{ 2354 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 2355 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 2356 } 2357 }, 2358 enableKernel: s.kernel, 2359 snapFiles: []snap.PlaceInfo{s.core20, s.kernel}, 2360 comment: "happy default no upgrades", 2361 }, 2362 2363 // happy upgrade cases 2364 { 2365 modeenv: &boot.Modeenv{ 2366 Mode: "run", 2367 Base: s.core20.Filename(), 2368 CurrentKernels: []string{s.kernel.Filename(), s.kernelr2.Filename()}, 2369 }, 2370 additionalMountsFunc: func() []systemdMount { 2371 return []systemdMount{ 2372 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 2373 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernelr2), 2374 } 2375 }, 2376 kernelStatus: boot.TryingStatus, 2377 enableKernel: s.kernel, 2378 enableTryKernel: s.kernelr2, 2379 snapFiles: []snap.PlaceInfo{s.core20, s.kernel, s.kernelr2}, 2380 comment: "happy kernel snap upgrade", 2381 }, 2382 { 2383 modeenv: &boot.Modeenv{ 2384 Mode: "run", 2385 Base: s.core20.Filename(), 2386 TryBase: s.core20r2.Filename(), 2387 BaseStatus: boot.TryStatus, 2388 CurrentKernels: []string{s.kernel.Filename()}, 2389 }, 2390 additionalMountsFunc: func() []systemdMount { 2391 return []systemdMount{ 2392 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20r2), 2393 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 2394 } 2395 }, 2396 enableKernel: s.kernel, 2397 snapFiles: []snap.PlaceInfo{s.kernel, s.core20, s.core20r2}, 2398 expModeenv: &boot.Modeenv{ 2399 Mode: "run", 2400 Base: s.core20.Filename(), 2401 TryBase: s.core20r2.Filename(), 2402 BaseStatus: boot.TryingStatus, 2403 CurrentKernels: []string{s.kernel.Filename()}, 2404 }, 2405 comment: "happy base snap upgrade", 2406 }, 2407 { 2408 modeenv: &boot.Modeenv{ 2409 Mode: "run", 2410 Base: s.core20.Filename(), 2411 TryBase: s.core20r2.Filename(), 2412 BaseStatus: boot.TryStatus, 2413 CurrentKernels: []string{s.kernel.Filename(), s.kernelr2.Filename()}, 2414 }, 2415 additionalMountsFunc: func() []systemdMount { 2416 return []systemdMount{ 2417 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20r2), 2418 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernelr2), 2419 } 2420 }, 2421 enableKernel: s.kernel, 2422 enableTryKernel: s.kernelr2, 2423 snapFiles: []snap.PlaceInfo{s.kernel, s.kernelr2, s.core20, s.core20r2}, 2424 kernelStatus: boot.TryingStatus, 2425 expModeenv: &boot.Modeenv{ 2426 Mode: "run", 2427 Base: s.core20.Filename(), 2428 TryBase: s.core20r2.Filename(), 2429 BaseStatus: boot.TryingStatus, 2430 CurrentKernels: []string{s.kernel.Filename(), s.kernelr2.Filename()}, 2431 }, 2432 comment: "happy simultaneous base snap and kernel snap upgrade", 2433 }, 2434 2435 // fallback cases 2436 { 2437 modeenv: &boot.Modeenv{ 2438 Mode: "run", 2439 Base: s.core20.Filename(), 2440 TryBase: s.core20r2.Filename(), 2441 BaseStatus: boot.TryStatus, 2442 CurrentKernels: []string{s.kernel.Filename()}, 2443 }, 2444 additionalMountsFunc: func() []systemdMount { 2445 return []systemdMount{ 2446 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 2447 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 2448 } 2449 }, 2450 enableKernel: s.kernel, 2451 snapFiles: []snap.PlaceInfo{s.kernel, s.core20}, 2452 comment: "happy fallback try base not existing", 2453 }, 2454 { 2455 modeenv: &boot.Modeenv{ 2456 Mode: "run", 2457 Base: s.core20.Filename(), 2458 BaseStatus: boot.TryStatus, 2459 TryBase: "", 2460 CurrentKernels: []string{s.kernel.Filename()}, 2461 }, 2462 additionalMountsFunc: func() []systemdMount { 2463 return []systemdMount{ 2464 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 2465 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 2466 } 2467 }, 2468 enableKernel: s.kernel, 2469 snapFiles: []snap.PlaceInfo{s.kernel, s.core20}, 2470 comment: "happy fallback base_status try, empty try_base", 2471 }, 2472 { 2473 modeenv: &boot.Modeenv{ 2474 Mode: "run", 2475 Base: s.core20.Filename(), 2476 TryBase: s.core20r2.Filename(), 2477 BaseStatus: boot.TryingStatus, 2478 CurrentKernels: []string{s.kernel.Filename()}, 2479 }, 2480 additionalMountsFunc: func() []systemdMount { 2481 return []systemdMount{ 2482 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 2483 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 2484 } 2485 }, 2486 enableKernel: s.kernel, 2487 snapFiles: []snap.PlaceInfo{s.kernel, s.core20, s.core20r2}, 2488 expModeenv: &boot.Modeenv{ 2489 Mode: "run", 2490 Base: s.core20.Filename(), 2491 TryBase: s.core20r2.Filename(), 2492 BaseStatus: boot.DefaultStatus, 2493 CurrentKernels: []string{s.kernel.Filename()}, 2494 }, 2495 comment: "happy fallback failed boot with try snap", 2496 }, 2497 { 2498 modeenv: &boot.Modeenv{ 2499 Mode: "run", 2500 Base: s.core20.Filename(), 2501 CurrentKernels: []string{s.kernel.Filename()}, 2502 }, 2503 enableKernel: s.kernel, 2504 enableTryKernel: s.kernelr2, 2505 snapFiles: []snap.PlaceInfo{s.core20, s.kernel, s.kernelr2}, 2506 kernelStatus: boot.TryingStatus, 2507 expRebootPanic: "reboot due to untrusted try kernel snap", 2508 comment: "happy fallback untrusted try kernel snap", 2509 }, 2510 // TODO:UC20: if we ever have a way to compare what kernel was booted, 2511 // and we compute that the booted kernel was the try kernel, 2512 // but the try kernel is not enabled on the bootloader 2513 // (somehow??), then this should become a reboot case rather 2514 // than mount the old kernel snap 2515 { 2516 modeenv: &boot.Modeenv{ 2517 Mode: "run", 2518 Base: s.core20.Filename(), 2519 CurrentKernels: []string{s.kernel.Filename()}, 2520 }, 2521 kernelStatus: boot.TryingStatus, 2522 enableKernel: s.kernel, 2523 snapFiles: []snap.PlaceInfo{s.core20, s.kernel}, 2524 expRebootPanic: "reboot due to no try kernel snap", 2525 comment: "happy fallback kernel_status trying no try kernel", 2526 }, 2527 2528 // unhappy cases 2529 { 2530 modeenv: &boot.Modeenv{ 2531 Mode: "run", 2532 }, 2533 expError: "fallback base snap unusable: cannot get snap revision: modeenv base boot variable is empty", 2534 comment: "unhappy empty modeenv", 2535 }, 2536 { 2537 modeenv: &boot.Modeenv{ 2538 Mode: "run", 2539 Base: s.core20.Filename(), 2540 CurrentKernels: []string{s.kernel.Filename()}, 2541 }, 2542 enableKernel: s.kernelr2, 2543 snapFiles: []snap.PlaceInfo{s.core20, s.kernelr2}, 2544 expError: fmt.Sprintf("fallback kernel snap %q is not trusted in the modeenv", s.kernelr2.Filename()), 2545 comment: "unhappy untrusted main kernel snap", 2546 }, 2547 } 2548 2549 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 2550 2551 for _, t := range tt { 2552 comment := Commentf(t.comment) 2553 2554 var cleanups []func() 2555 2556 if t.expRebootPanic != "" { 2557 r := boot.MockInitramfsReboot(func() error { 2558 panic(t.expRebootPanic) 2559 }) 2560 cleanups = append(cleanups, r) 2561 } 2562 2563 // setup unique root dir per test 2564 rootDir := c.MkDir() 2565 cleanups = append(cleanups, func() { dirs.SetRootDir(dirs.GlobalRootDir) }) 2566 dirs.SetRootDir(rootDir) 2567 2568 restore := disks.MockMountPointDisksToPartitionMapping( 2569 map[disks.Mountpoint]*disks.MockDiskMapping{ 2570 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootDisk, 2571 {Mountpoint: boot.InitramfsDataDir}: defaultBootDisk, 2572 }, 2573 ) 2574 cleanups = append(cleanups, restore) 2575 2576 // setup expected systemd-mount calls - every test case has ubuntu-boot, 2577 // ubuntu-seed and ubuntu-data mounts because all those mounts happen 2578 // before any boot logic 2579 mnts := []systemdMount{ 2580 ubuntuLabelMount("ubuntu-boot", "run"), 2581 ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"), 2582 ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"), 2583 } 2584 if t.additionalMountsFunc != nil { 2585 mnts = append(mnts, t.additionalMountsFunc()...) 2586 } 2587 cleanups = append(cleanups, s.mockSystemdMountSequence(c, mnts, comment)) 2588 2589 // mock a bootloader 2590 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 2591 bootloader.Force(bloader) 2592 cleanups = append(cleanups, func() { bootloader.Force(nil) }) 2593 2594 if t.enableKernel != nil { 2595 // don't need to restore since each test case has a unique bloader 2596 bloader.SetEnabledKernel(t.enableKernel) 2597 } 2598 2599 if t.enableTryKernel != nil { 2600 bloader.SetEnabledTryKernel(t.enableTryKernel) 2601 } 2602 2603 // set the kernel_status boot var 2604 err := bloader.SetBootVars(map[string]string{"kernel_status": t.kernelStatus}) 2605 c.Assert(err, IsNil, comment) 2606 2607 // write the initial modeenv 2608 err = t.modeenv.WriteTo(boot.InitramfsWritableDir) 2609 c.Assert(err, IsNil, comment) 2610 2611 // make the snap files - no restore needed because we use a unique root 2612 // dir for each test case 2613 makeSnapFilesOnEarlyBootUbuntuData(c, t.snapFiles...) 2614 2615 if t.expRebootPanic != "" { 2616 f := func() { main.Parser().ParseArgs([]string{"initramfs-mounts"}) } 2617 c.Assert(f, PanicMatches, t.expRebootPanic, comment) 2618 } else { 2619 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 2620 if t.expError != "" { 2621 c.Assert(err, ErrorMatches, t.expError, comment) 2622 } else { 2623 c.Assert(err, IsNil, comment) 2624 2625 // check the resultant modeenv 2626 // if the expModeenv is nil, we just compare to the start 2627 newModeenv, err := boot.ReadModeenv(boot.InitramfsWritableDir) 2628 c.Assert(err, IsNil, comment) 2629 m := t.modeenv 2630 if t.expModeenv != nil { 2631 m = t.expModeenv 2632 } 2633 c.Assert(newModeenv.BaseStatus, DeepEquals, m.BaseStatus, comment) 2634 c.Assert(newModeenv.TryBase, DeepEquals, m.TryBase, comment) 2635 c.Assert(newModeenv.Base, DeepEquals, m.Base, comment) 2636 } 2637 } 2638 2639 for _, r := range cleanups { 2640 r() 2641 } 2642 } 2643 } 2644 2645 func (s *initramfsMountsSuite) testRecoverModeHappy(c *C) { 2646 // ensure that we check that access to sealed keys were locked 2647 sealedKeysLocked := false 2648 restore := main.MockSecbootLockSealedKeys(func() error { 2649 sealedKeysLocked = true 2650 return nil 2651 }) 2652 defer restore() 2653 2654 // mock various files that are copied around during recover mode (and files 2655 // that shouldn't be copied around) 2656 ephemeralUbuntuData := filepath.Join(boot.InitramfsRunMntDir, "data/") 2657 err := os.MkdirAll(ephemeralUbuntuData, 0755) 2658 c.Assert(err, IsNil) 2659 // mock a auth data in the host's ubuntu-data 2660 hostUbuntuData := filepath.Join(boot.InitramfsRunMntDir, "host/ubuntu-data/") 2661 err = os.MkdirAll(hostUbuntuData, 0755) 2662 c.Assert(err, IsNil) 2663 mockCopiedFiles := []string{ 2664 // extrausers 2665 "system-data/var/lib/extrausers/passwd", 2666 "system-data/var/lib/extrausers/shadow", 2667 "system-data/var/lib/extrausers/group", 2668 "system-data/var/lib/extrausers/gshadow", 2669 // sshd 2670 "system-data/etc/ssh/ssh_host_rsa.key", 2671 "system-data/etc/ssh/ssh_host_rsa.key.pub", 2672 // user ssh 2673 "user-data/user1/.ssh/authorized_keys", 2674 "user-data/user2/.ssh/authorized_keys", 2675 // user snap authentication 2676 "user-data/user1/.snap/auth.json", 2677 // sudoers 2678 "system-data/etc/sudoers.d/create-user-test", 2679 // netplan networking 2680 "system-data/etc/netplan/00-snapd-config.yaml", // example console-conf filename 2681 "system-data/etc/netplan/50-cloud-init.yaml", // example cloud-init filename 2682 // systemd clock file 2683 "system-data/var/lib/systemd/timesync/clock", 2684 "system-data/etc/machine-id", // machine-id for systemd-networkd 2685 } 2686 mockUnrelatedFiles := []string{ 2687 "system-data/var/lib/foo", 2688 "system-data/etc/passwd", 2689 "user-data/user1/some-random-data", 2690 "user-data/user2/other-random-data", 2691 "user-data/user2/.snap/sneaky-not-auth.json", 2692 "system-data/etc/not-networking/netplan", 2693 "system-data/var/lib/systemd/timesync/clock-not-the-clock", 2694 "system-data/etc/machine-id-except-not", 2695 } 2696 for _, mockFile := range append(mockCopiedFiles, mockUnrelatedFiles...) { 2697 p := filepath.Join(hostUbuntuData, mockFile) 2698 err = os.MkdirAll(filepath.Dir(p), 0750) 2699 c.Assert(err, IsNil) 2700 mockContent := fmt.Sprintf("content of %s", filepath.Base(mockFile)) 2701 err = ioutil.WriteFile(p, []byte(mockContent), 0640) 2702 c.Assert(err, IsNil) 2703 } 2704 // create a mock state 2705 mockedState := filepath.Join(hostUbuntuData, "system-data/var/lib/snapd/state.json") 2706 err = os.MkdirAll(filepath.Dir(mockedState), 0750) 2707 c.Assert(err, IsNil) 2708 err = ioutil.WriteFile(mockedState, []byte(mockStateContent), 0640) 2709 c.Assert(err, IsNil) 2710 2711 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 2712 c.Assert(err, IsNil) 2713 2714 // we always need to lock access to sealed keys 2715 c.Check(sealedKeysLocked, Equals, true) 2716 2717 modeEnv := filepath.Join(ephemeralUbuntuData, "/system-data/var/lib/snapd/modeenv") 2718 c.Check(modeEnv, testutil.FileEquals, `mode=recover 2719 recovery_system=20191118 2720 base=core20_1.snap 2721 model=my-brand/my-model 2722 grade=signed 2723 `) 2724 for _, p := range mockUnrelatedFiles { 2725 c.Check(filepath.Join(ephemeralUbuntuData, p), testutil.FileAbsent) 2726 } 2727 for _, p := range mockCopiedFiles { 2728 c.Check(filepath.Join(ephemeralUbuntuData, p), testutil.FilePresent) 2729 fi, err := os.Stat(filepath.Join(ephemeralUbuntuData, p)) 2730 // check file mode is set 2731 c.Assert(err, IsNil) 2732 c.Check(fi.Mode(), Equals, os.FileMode(0640)) 2733 // check dir mode is set in parent dir 2734 fiParent, err := os.Stat(filepath.Dir(filepath.Join(ephemeralUbuntuData, p))) 2735 c.Assert(err, IsNil) 2736 c.Check(fiParent.Mode(), Equals, os.FileMode(os.ModeDir|0750)) 2737 } 2738 2739 c.Check(filepath.Join(ephemeralUbuntuData, "system-data/var/lib/snapd/state.json"), testutil.FileEquals, `{"data":{"auth":{"last-id":1,"macaroon-key":"not-a-cookie","users":[{"id":1,"name":"mvo"}]}},"changes":{},"tasks":{},"last-change-id":0,"last-task-id":0,"last-lane-id":0}`) 2740 2741 // finally check that the recovery system bootenv was updated to be in run 2742 // mode 2743 bloader, err := bootloader.Find("", nil) 2744 c.Assert(err, IsNil) 2745 m, err := bloader.GetBootVars("snapd_recovery_system", "snapd_recovery_mode") 2746 c.Assert(err, IsNil) 2747 c.Assert(m, DeepEquals, map[string]string{ 2748 "snapd_recovery_system": "20191118", 2749 "snapd_recovery_mode": "run", 2750 }) 2751 } 2752 2753 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeHappy(c *C) { 2754 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 2755 2756 // setup a bootloader for setting the bootenv after we are done 2757 bloader := bootloadertest.Mock("mock", c.MkDir()) 2758 bootloader.Force(bloader) 2759 defer bootloader.Force(nil) 2760 2761 // mock that we don't know which partition uuid the kernel was booted from 2762 restore := main.MockPartitionUUIDForBootedKernelDisk("") 2763 defer restore() 2764 2765 restore = disks.MockMountPointDisksToPartitionMapping( 2766 map[disks.Mountpoint]*disks.MockDiskMapping{ 2767 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultBootWithSaveDisk, 2768 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootWithSaveDisk, 2769 {Mountpoint: boot.InitramfsHostUbuntuDataDir}: defaultBootWithSaveDisk, 2770 {Mountpoint: boot.InitramfsUbuntuSaveDir}: defaultBootWithSaveDisk, 2771 }, 2772 ) 2773 defer restore() 2774 2775 restore = s.mockSystemdMountSequence(c, []systemdMount{ 2776 ubuntuLabelMount("ubuntu-seed", "recover"), 2777 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 2778 s.makeSeedSnapSystemdMount(snap.TypeKernel), 2779 s.makeSeedSnapSystemdMount(snap.TypeBase), 2780 { 2781 "tmpfs", 2782 boot.InitramfsDataDir, 2783 tmpfsMountOpts, 2784 }, 2785 { 2786 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 2787 boot.InitramfsUbuntuBootDir, 2788 needsFsckDiskMountOpts, 2789 }, 2790 { 2791 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 2792 boot.InitramfsHostUbuntuDataDir, 2793 needsNoSuidDiskMountOpts, 2794 }, 2795 { 2796 "/dev/disk/by-partuuid/ubuntu-save-partuuid", 2797 boot.InitramfsUbuntuSaveDir, 2798 nil, 2799 }, 2800 }, nil) 2801 defer restore() 2802 2803 s.testRecoverModeHappy(c) 2804 2805 // we should not have written a degraded.json 2806 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "degraded.json"), testutil.FileAbsent) 2807 2808 // we also should have written an empty boot-flags file 2809 c.Assert(filepath.Join(dirs.SnapRunDir, "boot-flags"), testutil.FileEquals, "") 2810 } 2811 2812 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeTimeMovesForwardHappy(c *C) { 2813 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 2814 2815 for _, tc := range s.timeTestCases() { 2816 comment := Commentf(tc.comment) 2817 cleanups := []func(){} 2818 2819 // always remove the ubuntu-seed dir, otherwise setupSeed complains the 2820 // model file already exists and can't setup the seed 2821 err := os.RemoveAll(filepath.Join(boot.InitramfsUbuntuSeedDir)) 2822 c.Assert(err, IsNil, comment) 2823 2824 // also always remove the data dir, since we need to copy state.json 2825 // there, so if the file already exists the initramfs code dies 2826 err = os.RemoveAll(filepath.Join(boot.InitramfsDataDir)) 2827 c.Assert(err, IsNil, comment) 2828 2829 s.setupSeed(c, tc.modelTime, nil) 2830 2831 restore := main.MockTimeNow(func() time.Time { 2832 return tc.now 2833 }) 2834 cleanups = append(cleanups, restore) 2835 2836 restore = disks.MockMountPointDisksToPartitionMapping( 2837 map[disks.Mountpoint]*disks.MockDiskMapping{ 2838 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultBootWithSaveDisk, 2839 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootWithSaveDisk, 2840 {Mountpoint: boot.InitramfsHostUbuntuDataDir}: defaultBootWithSaveDisk, 2841 {Mountpoint: boot.InitramfsUbuntuSaveDir}: defaultBootWithSaveDisk, 2842 }, 2843 ) 2844 cleanups = append(cleanups, restore) 2845 osutilSetTimeCalls := 0 2846 // check what time we try to move forward to 2847 restore = main.MockOsutilSetTime(func(t time.Time) error { 2848 osutilSetTimeCalls++ 2849 // make sure the timestamps are within 1 second of each other, they 2850 // won't be equal since the timestamp is serialized to an assertion and 2851 // read back 2852 tTrunc := t.Truncate(2 * time.Second) 2853 expTTrunc := tc.expT.Truncate(2 * time.Second) 2854 c.Assert(tTrunc.Equal(expTTrunc), Equals, true, Commentf("%s, exp %s, got %s", tc.comment, t, s.snapDeclAssertsTime)) 2855 return nil 2856 }) 2857 cleanups = append(cleanups, restore) 2858 2859 restore = s.mockSystemdMountSequence(c, []systemdMount{ 2860 ubuntuLabelMount("ubuntu-seed", "recover"), 2861 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 2862 s.makeSeedSnapSystemdMount(snap.TypeKernel), 2863 s.makeSeedSnapSystemdMount(snap.TypeBase), 2864 { 2865 "tmpfs", 2866 boot.InitramfsDataDir, 2867 tmpfsMountOpts, 2868 }, 2869 { 2870 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 2871 boot.InitramfsUbuntuBootDir, 2872 needsFsckDiskMountOpts, 2873 }, 2874 { 2875 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 2876 boot.InitramfsHostUbuntuDataDir, 2877 needsNoSuidDiskMountOpts, 2878 }, 2879 { 2880 "/dev/disk/by-partuuid/ubuntu-save-partuuid", 2881 boot.InitramfsUbuntuSaveDir, 2882 nil, 2883 }, 2884 }, nil) 2885 cleanups = append(cleanups, restore) 2886 2887 bloader := bootloadertest.Mock("mock", c.MkDir()) 2888 bootloader.Force(bloader) 2889 cleanups = append(cleanups, func() { bootloader.Force(nil) }) 2890 2891 s.testRecoverModeHappy(c) 2892 c.Assert(osutilSetTimeCalls, Equals, tc.setTimeCalls) 2893 2894 for _, r := range cleanups { 2895 r() 2896 } 2897 } 2898 } 2899 2900 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeGadgetDefaultsHappy(c *C) { 2901 // setup a seed with default gadget yaml 2902 const gadgetYamlDefaults = ` 2903 defaults: 2904 system: 2905 service: 2906 rsyslog.disable: true 2907 ssh.disable: true 2908 console-conf.disable: true 2909 journal.persistent: true 2910 ` 2911 c.Assert(os.RemoveAll(s.seedDir), IsNil) 2912 2913 s.setupSeed(c, time.Time{}, [][]string{ 2914 {"meta/gadget.yaml", gadgetYamlDefaults}, 2915 }) 2916 2917 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 2918 2919 // setup a bootloader for setting the bootenv after we are done 2920 bloader := bootloadertest.Mock("mock", c.MkDir()) 2921 bootloader.Force(bloader) 2922 defer bootloader.Force(nil) 2923 2924 // mock that we don't know which partition uuid the kernel was booted from 2925 restore := main.MockPartitionUUIDForBootedKernelDisk("") 2926 defer restore() 2927 2928 restore = disks.MockMountPointDisksToPartitionMapping( 2929 map[disks.Mountpoint]*disks.MockDiskMapping{ 2930 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultBootWithSaveDisk, 2931 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootWithSaveDisk, 2932 {Mountpoint: boot.InitramfsHostUbuntuDataDir}: defaultBootWithSaveDisk, 2933 {Mountpoint: boot.InitramfsUbuntuSaveDir}: defaultBootWithSaveDisk, 2934 }, 2935 ) 2936 defer restore() 2937 2938 restore = s.mockSystemdMountSequence(c, []systemdMount{ 2939 ubuntuLabelMount("ubuntu-seed", "recover"), 2940 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 2941 s.makeSeedSnapSystemdMount(snap.TypeKernel), 2942 s.makeSeedSnapSystemdMount(snap.TypeBase), 2943 { 2944 "tmpfs", 2945 boot.InitramfsDataDir, 2946 tmpfsMountOpts, 2947 }, 2948 { 2949 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 2950 boot.InitramfsUbuntuBootDir, 2951 needsFsckDiskMountOpts, 2952 }, 2953 { 2954 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 2955 boot.InitramfsHostUbuntuDataDir, 2956 needsNoSuidDiskMountOpts, 2957 }, 2958 { 2959 "/dev/disk/by-partuuid/ubuntu-save-partuuid", 2960 boot.InitramfsUbuntuSaveDir, 2961 nil, 2962 }, 2963 }, nil) 2964 defer restore() 2965 2966 // we will call out to systemctl in the initramfs, but only using --root 2967 // which doesn't talk to systemd, just manipulates files around 2968 var sysctlArgs [][]string 2969 systemctlRestorer := systemd.MockSystemctl(func(args ...string) (buf []byte, err error) { 2970 sysctlArgs = append(sysctlArgs, args) 2971 return nil, nil 2972 }) 2973 defer systemctlRestorer() 2974 2975 s.testRecoverModeHappy(c) 2976 2977 // we should not have written a degraded.json 2978 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "degraded.json"), testutil.FileAbsent) 2979 2980 c.Assert(osutil.FileExists(filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled")), Equals, true) 2981 2982 // check that everything from the gadget defaults was setup 2983 c.Assert(osutil.FileExists(filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/ssh/sshd_not_to_be_run")), Equals, true) 2984 c.Assert(osutil.FileExists(filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/var/lib/console-conf/complete")), Equals, true) 2985 exists, _, _ := osutil.DirExists(filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/var/log/journal")) 2986 c.Assert(exists, Equals, true) 2987 2988 // systemctl was called the way we expect 2989 c.Assert(sysctlArgs, DeepEquals, [][]string{{"--root", filepath.Join(boot.InitramfsWritableDir, "_writable_defaults"), "mask", "rsyslog.service"}}) 2990 } 2991 2992 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeHappyBootedKernelPartitionUUID(c *C) { 2993 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 2994 2995 restore := main.MockPartitionUUIDForBootedKernelDisk("specific-ubuntu-seed-partuuid") 2996 defer restore() 2997 2998 // setup a bootloader for setting the bootenv after we are done 2999 bloader := bootloadertest.Mock("mock", c.MkDir()) 3000 bootloader.Force(bloader) 3001 defer bootloader.Force(nil) 3002 3003 restore = disks.MockMountPointDisksToPartitionMapping( 3004 map[disks.Mountpoint]*disks.MockDiskMapping{ 3005 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultBootWithSaveDisk, 3006 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootWithSaveDisk, 3007 {Mountpoint: boot.InitramfsHostUbuntuDataDir}: defaultBootWithSaveDisk, 3008 {Mountpoint: boot.InitramfsUbuntuSaveDir}: defaultBootWithSaveDisk, 3009 }, 3010 ) 3011 defer restore() 3012 3013 restore = s.mockSystemdMountSequence(c, []systemdMount{ 3014 { 3015 "/dev/disk/by-partuuid/specific-ubuntu-seed-partuuid", 3016 boot.InitramfsUbuntuSeedDir, 3017 needsFsckDiskMountOpts, 3018 }, 3019 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 3020 s.makeSeedSnapSystemdMount(snap.TypeKernel), 3021 s.makeSeedSnapSystemdMount(snap.TypeBase), 3022 { 3023 "tmpfs", 3024 boot.InitramfsDataDir, 3025 tmpfsMountOpts, 3026 }, 3027 { 3028 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 3029 boot.InitramfsUbuntuBootDir, 3030 needsFsckDiskMountOpts, 3031 }, 3032 { 3033 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 3034 boot.InitramfsHostUbuntuDataDir, 3035 needsNoSuidDiskMountOpts, 3036 }, 3037 { 3038 "/dev/disk/by-partuuid/ubuntu-save-partuuid", 3039 boot.InitramfsUbuntuSaveDir, 3040 nil, 3041 }, 3042 }, nil) 3043 defer restore() 3044 3045 s.testRecoverModeHappy(c) 3046 3047 // we should not have written a degraded.json 3048 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "degraded.json"), testutil.FileAbsent) 3049 } 3050 3051 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeHappyEncrypted(c *C) { 3052 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 3053 3054 restore := main.MockPartitionUUIDForBootedKernelDisk("") 3055 defer restore() 3056 3057 // setup a bootloader for setting the bootenv after we are done 3058 bloader := bootloadertest.Mock("mock", c.MkDir()) 3059 bootloader.Force(bloader) 3060 defer bootloader.Force(nil) 3061 3062 restore = disks.MockMountPointDisksToPartitionMapping( 3063 map[disks.Mountpoint]*disks.MockDiskMapping{ 3064 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultEncBootDisk, 3065 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultEncBootDisk, 3066 { 3067 Mountpoint: boot.InitramfsHostUbuntuDataDir, 3068 IsDecryptedDevice: true, 3069 }: defaultEncBootDisk, 3070 { 3071 Mountpoint: boot.InitramfsUbuntuSaveDir, 3072 IsDecryptedDevice: true, 3073 }: defaultEncBootDisk, 3074 }, 3075 ) 3076 defer restore() 3077 3078 dataActivated := false 3079 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 3080 c.Assert(name, Equals, "ubuntu-data") 3081 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")) 3082 3083 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 3084 c.Assert(err, IsNil) 3085 c.Assert(encDevPartUUID, Equals, "ubuntu-data-enc-partuuid") 3086 c.Assert(opts.AllowRecoveryKey, Equals, false) 3087 c.Assert(opts.WhichModel, NotNil) 3088 mod, err := opts.WhichModel() 3089 c.Assert(err, IsNil) 3090 c.Check(mod.Model(), Equals, "my-model") 3091 3092 dataActivated = true 3093 return happyUnlocked("ubuntu-data", secboot.UnlockedWithSealedKey), nil 3094 }) 3095 defer restore() 3096 3097 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsHostWritableDir, "foo", "marker") 3098 s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker") 3099 3100 saveActivated := false 3101 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 3102 c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not activated yet")) 3103 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 3104 c.Assert(err, IsNil) 3105 c.Assert(encDevPartUUID, Equals, "ubuntu-save-enc-partuuid") 3106 c.Assert(key, DeepEquals, []byte("foo")) 3107 saveActivated = true 3108 return happyUnlocked("ubuntu-save", secboot.UnlockedWithKey), nil 3109 }) 3110 defer restore() 3111 3112 measureEpochCalls := 0 3113 measureModelCalls := 0 3114 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 3115 measureEpochCalls++ 3116 return nil 3117 }) 3118 defer restore() 3119 3120 var measuredModel *asserts.Model 3121 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 3122 measureModelCalls++ 3123 var err error 3124 measuredModel, err = findModel() 3125 if err != nil { 3126 return err 3127 } 3128 return nil 3129 }) 3130 defer restore() 3131 3132 restore = s.mockSystemdMountSequence(c, []systemdMount{ 3133 ubuntuLabelMount("ubuntu-seed", "recover"), 3134 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 3135 s.makeSeedSnapSystemdMount(snap.TypeKernel), 3136 s.makeSeedSnapSystemdMount(snap.TypeBase), 3137 { 3138 "tmpfs", 3139 boot.InitramfsDataDir, 3140 tmpfsMountOpts, 3141 }, 3142 { 3143 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 3144 boot.InitramfsUbuntuBootDir, 3145 needsFsckDiskMountOpts, 3146 }, 3147 { 3148 "/dev/mapper/ubuntu-data-random", 3149 boot.InitramfsHostUbuntuDataDir, 3150 needsNoSuidDiskMountOpts, 3151 }, 3152 { 3153 "/dev/mapper/ubuntu-save-random", 3154 boot.InitramfsUbuntuSaveDir, 3155 nil, 3156 }, 3157 }, nil) 3158 defer restore() 3159 3160 s.testRecoverModeHappy(c) 3161 3162 // we should not have written a degraded.json 3163 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "degraded.json"), testutil.FileAbsent) 3164 3165 c.Check(dataActivated, Equals, true) 3166 c.Check(saveActivated, Equals, true) 3167 c.Check(measureEpochCalls, Equals, 1) 3168 c.Check(measureModelCalls, Equals, 1) 3169 c.Check(measuredModel, DeepEquals, s.model) 3170 3171 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 3172 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent) 3173 } 3174 3175 func checkDegradedJSON(c *C, exp map[string]interface{}) { 3176 b, err := ioutil.ReadFile(filepath.Join(dirs.SnapBootstrapRunDir, "degraded.json")) 3177 c.Assert(err, IsNil) 3178 degradedJSONObj := make(map[string]interface{}) 3179 err = json.Unmarshal(b, °radedJSONObj) 3180 c.Assert(err, IsNil) 3181 3182 c.Assert(degradedJSONObj, DeepEquals, exp) 3183 } 3184 3185 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedDataUnlockFallbackHappy(c *C) { 3186 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 3187 3188 restore := main.MockPartitionUUIDForBootedKernelDisk("") 3189 defer restore() 3190 3191 // setup a bootloader for setting the bootenv after we are done 3192 bloader := bootloadertest.Mock("mock", c.MkDir()) 3193 bootloader.Force(bloader) 3194 defer bootloader.Force(nil) 3195 3196 restore = disks.MockMountPointDisksToPartitionMapping( 3197 map[disks.Mountpoint]*disks.MockDiskMapping{ 3198 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultEncBootDisk, 3199 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultEncBootDisk, 3200 { 3201 Mountpoint: boot.InitramfsHostUbuntuDataDir, 3202 IsDecryptedDevice: true, 3203 }: defaultEncBootDisk, 3204 { 3205 Mountpoint: boot.InitramfsUbuntuSaveDir, 3206 IsDecryptedDevice: true, 3207 }: defaultEncBootDisk, 3208 }, 3209 ) 3210 defer restore() 3211 3212 dataActivated := false 3213 saveActivated := false 3214 unlockVolumeWithSealedKeyCalls := 0 3215 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 3216 unlockVolumeWithSealedKeyCalls++ 3217 switch unlockVolumeWithSealedKeyCalls { 3218 3219 case 1: 3220 // pretend we can't unlock ubuntu-data with the main run key 3221 c.Assert(name, Equals, "ubuntu-data") 3222 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")) 3223 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 3224 c.Assert(err, IsNil) 3225 c.Assert(encDevPartUUID, Equals, "ubuntu-data-enc-partuuid") 3226 c.Assert(opts.AllowRecoveryKey, Equals, false) 3227 c.Assert(opts.WhichModel, NotNil) 3228 return foundEncrypted("ubuntu-data"), fmt.Errorf("failed to unlock ubuntu-data") 3229 3230 case 2: 3231 // now we can unlock ubuntu-data with the fallback key 3232 c.Assert(name, Equals, "ubuntu-data") 3233 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key")) 3234 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 3235 c.Assert(err, IsNil) 3236 c.Assert(encDevPartUUID, Equals, "ubuntu-data-enc-partuuid") 3237 c.Assert(opts.AllowRecoveryKey, Equals, true) 3238 c.Assert(opts.WhichModel, NotNil) 3239 mod, err := opts.WhichModel() 3240 c.Assert(err, IsNil) 3241 c.Check(mod.Model(), Equals, "my-model") 3242 3243 dataActivated = true 3244 return happyUnlocked("ubuntu-data", secboot.UnlockedWithSealedKey), nil 3245 default: 3246 c.Errorf("unexpected call to UnlockVolumeUsingSealedKeyIfEncrypted (num %d)", unlockVolumeWithSealedKeyCalls) 3247 return secboot.UnlockResult{}, fmt.Errorf("broken test") 3248 } 3249 }) 3250 defer restore() 3251 3252 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsHostWritableDir, "foo", "marker") 3253 s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker") 3254 3255 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 3256 c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not activated yet")) 3257 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 3258 c.Assert(err, IsNil) 3259 c.Assert(encDevPartUUID, Equals, "ubuntu-save-enc-partuuid") 3260 c.Assert(key, DeepEquals, []byte("foo")) 3261 saveActivated = true 3262 return happyUnlocked("ubuntu-save", secboot.UnlockedWithKey), nil 3263 }) 3264 defer restore() 3265 3266 measureEpochCalls := 0 3267 measureModelCalls := 0 3268 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 3269 measureEpochCalls++ 3270 return nil 3271 }) 3272 defer restore() 3273 3274 var measuredModel *asserts.Model 3275 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 3276 measureModelCalls++ 3277 var err error 3278 measuredModel, err = findModel() 3279 if err != nil { 3280 return err 3281 } 3282 return nil 3283 }) 3284 defer restore() 3285 3286 restore = s.mockSystemdMountSequence(c, []systemdMount{ 3287 ubuntuLabelMount("ubuntu-seed", "recover"), 3288 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 3289 s.makeSeedSnapSystemdMount(snap.TypeKernel), 3290 s.makeSeedSnapSystemdMount(snap.TypeBase), 3291 { 3292 "tmpfs", 3293 boot.InitramfsDataDir, 3294 tmpfsMountOpts, 3295 }, 3296 { 3297 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 3298 boot.InitramfsUbuntuBootDir, 3299 needsFsckDiskMountOpts, 3300 }, 3301 { 3302 "/dev/mapper/ubuntu-data-random", 3303 boot.InitramfsHostUbuntuDataDir, 3304 needsNoSuidDiskMountOpts, 3305 }, 3306 { 3307 "/dev/mapper/ubuntu-save-random", 3308 boot.InitramfsUbuntuSaveDir, 3309 nil, 3310 }, 3311 }, nil) 3312 defer restore() 3313 3314 s.testRecoverModeHappy(c) 3315 3316 checkDegradedJSON(c, map[string]interface{}{ 3317 "ubuntu-boot": map[string]interface{}{ 3318 "find-state": "found", 3319 "mount-state": "mounted", 3320 "device": "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 3321 "mount-location": boot.InitramfsUbuntuBootDir, 3322 }, 3323 "ubuntu-data": map[string]interface{}{ 3324 "device": "/dev/mapper/ubuntu-data-random", 3325 "unlock-state": "unlocked", 3326 "find-state": "found", 3327 "mount-state": "mounted", 3328 "unlock-key": "fallback", 3329 "mount-location": boot.InitramfsHostUbuntuDataDir, 3330 }, 3331 "ubuntu-save": map[string]interface{}{ 3332 "device": "/dev/mapper/ubuntu-save-random", 3333 "unlock-key": "run", 3334 "unlock-state": "unlocked", 3335 "mount-state": "mounted", 3336 "find-state": "found", 3337 "mount-location": boot.InitramfsUbuntuSaveDir, 3338 }, 3339 "error-log": []interface{}{ 3340 "cannot unlock encrypted ubuntu-data (device /dev/disk/by-partuuid/ubuntu-data-enc-partuuid) with sealed run key: failed to unlock ubuntu-data", 3341 }, 3342 }) 3343 3344 c.Check(dataActivated, Equals, true) 3345 c.Check(unlockVolumeWithSealedKeyCalls, Equals, 2) 3346 c.Check(saveActivated, Equals, true) 3347 c.Check(measureEpochCalls, Equals, 1) 3348 c.Check(measureModelCalls, Equals, 1) 3349 c.Check(measuredModel, DeepEquals, s.model) 3350 3351 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 3352 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent) 3353 } 3354 3355 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedSaveUnlockFallbackHappy(c *C) { 3356 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 3357 3358 restore := main.MockPartitionUUIDForBootedKernelDisk("") 3359 defer restore() 3360 3361 // setup a bootloader for setting the bootenv after we are done 3362 bloader := bootloadertest.Mock("mock", c.MkDir()) 3363 bootloader.Force(bloader) 3364 defer bootloader.Force(nil) 3365 3366 restore = disks.MockMountPointDisksToPartitionMapping( 3367 map[disks.Mountpoint]*disks.MockDiskMapping{ 3368 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultEncBootDisk, 3369 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultEncBootDisk, 3370 { 3371 Mountpoint: boot.InitramfsHostUbuntuDataDir, 3372 IsDecryptedDevice: true, 3373 }: defaultEncBootDisk, 3374 { 3375 Mountpoint: boot.InitramfsUbuntuSaveDir, 3376 IsDecryptedDevice: true, 3377 }: defaultEncBootDisk, 3378 }, 3379 ) 3380 defer restore() 3381 3382 dataActivated := false 3383 saveActivationAttempted := false 3384 unlockVolumeWithSealedKeyCalls := 0 3385 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 3386 unlockVolumeWithSealedKeyCalls++ 3387 switch unlockVolumeWithSealedKeyCalls { 3388 3389 case 1: 3390 // ubuntu data can be unlocked fine 3391 c.Assert(name, Equals, "ubuntu-data") 3392 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")) 3393 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 3394 c.Assert(err, IsNil) 3395 c.Assert(encDevPartUUID, Equals, "ubuntu-data-enc-partuuid") 3396 c.Assert(opts.AllowRecoveryKey, Equals, false) 3397 c.Assert(opts.WhichModel, NotNil) 3398 dataActivated = true 3399 return happyUnlocked("ubuntu-data", secboot.UnlockedWithSealedKey), nil 3400 3401 case 2: 3402 // then after ubuntu-save is attempted to be unlocked with the 3403 // unsealed run object on the encrypted data partition, we fall back 3404 // to using the sealed object on ubuntu-seed for save 3405 c.Assert(saveActivationAttempted, Equals, true) 3406 c.Assert(name, Equals, "ubuntu-save") 3407 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key")) 3408 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 3409 c.Assert(err, IsNil) 3410 c.Assert(encDevPartUUID, Equals, "ubuntu-save-enc-partuuid") 3411 c.Assert(opts.AllowRecoveryKey, Equals, true) 3412 c.Assert(opts.WhichModel, NotNil) 3413 mod, err := opts.WhichModel() 3414 c.Assert(err, IsNil) 3415 c.Check(mod.Model(), Equals, "my-model") 3416 dataActivated = true 3417 return happyUnlocked("ubuntu-save", secboot.UnlockedWithSealedKey), nil 3418 default: 3419 c.Errorf("unexpected call to UnlockVolumeUsingSealedKeyIfEncrypted (num %d)", unlockVolumeWithSealedKeyCalls) 3420 return secboot.UnlockResult{}, fmt.Errorf("broken test") 3421 } 3422 }) 3423 defer restore() 3424 3425 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsHostWritableDir, "foo", "marker") 3426 s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker") 3427 3428 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 3429 c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not activated yet")) 3430 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 3431 c.Assert(err, IsNil) 3432 c.Assert(encDevPartUUID, Equals, "ubuntu-save-enc-partuuid") 3433 c.Assert(key, DeepEquals, []byte("foo")) 3434 saveActivationAttempted = true 3435 return foundEncrypted("ubuntu-save"), fmt.Errorf("failed to unlock ubuntu-save with run object") 3436 }) 3437 defer restore() 3438 3439 measureEpochCalls := 0 3440 measureModelCalls := 0 3441 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 3442 measureEpochCalls++ 3443 return nil 3444 }) 3445 defer restore() 3446 3447 var measuredModel *asserts.Model 3448 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 3449 measureModelCalls++ 3450 var err error 3451 measuredModel, err = findModel() 3452 if err != nil { 3453 return err 3454 } 3455 return nil 3456 }) 3457 defer restore() 3458 3459 restore = s.mockSystemdMountSequence(c, []systemdMount{ 3460 ubuntuLabelMount("ubuntu-seed", "recover"), 3461 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 3462 s.makeSeedSnapSystemdMount(snap.TypeKernel), 3463 s.makeSeedSnapSystemdMount(snap.TypeBase), 3464 { 3465 "tmpfs", 3466 boot.InitramfsDataDir, 3467 tmpfsMountOpts, 3468 }, 3469 { 3470 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 3471 boot.InitramfsUbuntuBootDir, 3472 needsFsckDiskMountOpts, 3473 }, 3474 { 3475 "/dev/mapper/ubuntu-data-random", 3476 boot.InitramfsHostUbuntuDataDir, 3477 needsNoSuidDiskMountOpts, 3478 }, 3479 { 3480 "/dev/mapper/ubuntu-save-random", 3481 boot.InitramfsUbuntuSaveDir, 3482 nil, 3483 }, 3484 }, nil) 3485 defer restore() 3486 3487 s.testRecoverModeHappy(c) 3488 3489 checkDegradedJSON(c, map[string]interface{}{ 3490 "ubuntu-boot": map[string]interface{}{ 3491 "find-state": "found", 3492 "mount-state": "mounted", 3493 "device": "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 3494 "mount-location": boot.InitramfsUbuntuBootDir, 3495 }, 3496 "ubuntu-data": map[string]interface{}{ 3497 "device": "/dev/mapper/ubuntu-data-random", 3498 "unlock-state": "unlocked", 3499 "find-state": "found", 3500 "mount-state": "mounted", 3501 "unlock-key": "run", 3502 "mount-location": boot.InitramfsHostUbuntuDataDir, 3503 }, 3504 "ubuntu-save": map[string]interface{}{ 3505 "device": "/dev/mapper/ubuntu-save-random", 3506 "unlock-key": "fallback", 3507 "unlock-state": "unlocked", 3508 "mount-state": "mounted", 3509 "find-state": "found", 3510 "mount-location": boot.InitramfsUbuntuSaveDir, 3511 }, 3512 "error-log": []interface{}{ 3513 "cannot unlock encrypted ubuntu-save (device /dev/disk/by-partuuid/ubuntu-save-enc-partuuid) with sealed run key: failed to unlock ubuntu-save with run object", 3514 }, 3515 }) 3516 3517 c.Check(dataActivated, Equals, true) 3518 c.Check(unlockVolumeWithSealedKeyCalls, Equals, 2) 3519 c.Check(saveActivationAttempted, Equals, true) 3520 c.Check(measureEpochCalls, Equals, 1) 3521 c.Check(measureModelCalls, Equals, 1) 3522 c.Check(measuredModel, DeepEquals, s.model) 3523 3524 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 3525 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent) 3526 } 3527 3528 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedAbsentBootDataUnlockFallbackHappy(c *C) { 3529 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 3530 3531 restore := main.MockPartitionUUIDForBootedKernelDisk("") 3532 defer restore() 3533 3534 // setup a bootloader for setting the bootenv after we are done 3535 bloader := bootloadertest.Mock("mock", c.MkDir()) 3536 bootloader.Force(bloader) 3537 defer bootloader.Force(nil) 3538 3539 defaultEncDiskNoBoot := &disks.MockDiskMapping{ 3540 FilesystemLabelToPartUUID: map[string]string{ 3541 "ubuntu-seed": "ubuntu-seed-partuuid", 3542 "ubuntu-data-enc": "ubuntu-data-enc-partuuid", 3543 "ubuntu-save-enc": "ubuntu-save-enc-partuuid", 3544 }, 3545 DiskHasPartitions: true, 3546 DevNum: "defaultEncDevNoBoot", 3547 } 3548 3549 restore = disks.MockMountPointDisksToPartitionMapping( 3550 map[disks.Mountpoint]*disks.MockDiskMapping{ 3551 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultEncDiskNoBoot, 3552 // no ubuntu-boot so we fall back to unlocking data with fallback 3553 // key right away 3554 { 3555 Mountpoint: boot.InitramfsHostUbuntuDataDir, 3556 IsDecryptedDevice: true, 3557 }: defaultEncDiskNoBoot, 3558 { 3559 Mountpoint: boot.InitramfsUbuntuSaveDir, 3560 IsDecryptedDevice: true, 3561 }: defaultEncDiskNoBoot, 3562 }, 3563 ) 3564 defer restore() 3565 3566 dataActivated := false 3567 unlockVolumeWithSealedKeyCalls := 0 3568 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 3569 unlockVolumeWithSealedKeyCalls++ 3570 switch unlockVolumeWithSealedKeyCalls { 3571 case 1: 3572 // we skip trying to unlock with run key on ubuntu-boot and go 3573 // directly to using the fallback key on ubuntu-seed 3574 c.Assert(name, Equals, "ubuntu-data") 3575 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key")) 3576 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 3577 c.Assert(err, IsNil) 3578 c.Assert(encDevPartUUID, Equals, "ubuntu-data-enc-partuuid") 3579 c.Assert(opts.AllowRecoveryKey, Equals, true) 3580 c.Assert(opts.WhichModel, NotNil) 3581 dataActivated = true 3582 return happyUnlocked("ubuntu-data", secboot.UnlockedWithSealedKey), nil 3583 default: 3584 c.Errorf("unexpected call to UnlockVolumeUsingSealedKeyIfEncrypted (num %d)", unlockVolumeWithSealedKeyCalls) 3585 return secboot.UnlockResult{}, fmt.Errorf("broken test") 3586 } 3587 }) 3588 defer restore() 3589 3590 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsHostWritableDir, "foo", "marker") 3591 s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker") 3592 3593 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 3594 c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not activated yet")) 3595 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 3596 c.Assert(err, IsNil) 3597 c.Assert(encDevPartUUID, Equals, "ubuntu-save-enc-partuuid") 3598 c.Assert(key, DeepEquals, []byte("foo")) 3599 return happyUnlocked("ubuntu-save", secboot.UnlockedWithKey), nil 3600 }) 3601 defer restore() 3602 3603 measureEpochCalls := 0 3604 measureModelCalls := 0 3605 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 3606 measureEpochCalls++ 3607 return nil 3608 }) 3609 defer restore() 3610 3611 var measuredModel *asserts.Model 3612 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 3613 measureModelCalls++ 3614 var err error 3615 measuredModel, err = findModel() 3616 if err != nil { 3617 return err 3618 } 3619 return nil 3620 }) 3621 defer restore() 3622 3623 restore = s.mockSystemdMountSequence(c, []systemdMount{ 3624 ubuntuLabelMount("ubuntu-seed", "recover"), 3625 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 3626 s.makeSeedSnapSystemdMount(snap.TypeKernel), 3627 s.makeSeedSnapSystemdMount(snap.TypeBase), 3628 { 3629 "tmpfs", 3630 boot.InitramfsDataDir, 3631 tmpfsMountOpts, 3632 }, 3633 // no ubuntu-boot 3634 { 3635 "/dev/mapper/ubuntu-data-random", 3636 boot.InitramfsHostUbuntuDataDir, 3637 needsNoSuidDiskMountOpts, 3638 }, 3639 { 3640 "/dev/mapper/ubuntu-save-random", 3641 boot.InitramfsUbuntuSaveDir, 3642 nil, 3643 }, 3644 }, nil) 3645 defer restore() 3646 3647 s.testRecoverModeHappy(c) 3648 3649 checkDegradedJSON(c, map[string]interface{}{ 3650 "ubuntu-boot": map[string]interface{}{ 3651 "find-state": "not-found", 3652 }, 3653 "ubuntu-data": map[string]interface{}{ 3654 "device": "/dev/mapper/ubuntu-data-random", 3655 "unlock-state": "unlocked", 3656 "find-state": "found", 3657 "mount-state": "mounted", 3658 "unlock-key": "fallback", 3659 "mount-location": boot.InitramfsHostUbuntuDataDir, 3660 }, 3661 "ubuntu-save": map[string]interface{}{ 3662 "device": "/dev/mapper/ubuntu-save-random", 3663 "unlock-key": "run", 3664 "unlock-state": "unlocked", 3665 "mount-state": "mounted", 3666 "find-state": "found", 3667 "mount-location": boot.InitramfsUbuntuSaveDir, 3668 }, 3669 "error-log": []interface{}{ 3670 "cannot find ubuntu-boot partition on disk defaultEncDevNoBoot", 3671 }, 3672 }) 3673 3674 c.Check(dataActivated, Equals, true) 3675 c.Check(unlockVolumeWithSealedKeyCalls, Equals, 1) 3676 c.Check(measureEpochCalls, Equals, 1) 3677 c.Check(measureModelCalls, Equals, 1) 3678 c.Check(measuredModel, DeepEquals, s.model) 3679 3680 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 3681 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent) 3682 } 3683 3684 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedAbsentBootDataUnlockRecoveryKeyHappy(c *C) { 3685 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 3686 3687 restore := main.MockPartitionUUIDForBootedKernelDisk("") 3688 defer restore() 3689 3690 // setup a bootloader for setting the bootenv after we are done 3691 bloader := bootloadertest.Mock("mock", c.MkDir()) 3692 bootloader.Force(bloader) 3693 defer bootloader.Force(nil) 3694 3695 defaultEncDiskNoBoot := &disks.MockDiskMapping{ 3696 FilesystemLabelToPartUUID: map[string]string{ 3697 "ubuntu-seed": "ubuntu-seed-partuuid", 3698 "ubuntu-data-enc": "ubuntu-data-enc-partuuid", 3699 "ubuntu-save-enc": "ubuntu-save-enc-partuuid", 3700 }, 3701 DiskHasPartitions: true, 3702 DevNum: "defaultEncDevNoBoot", 3703 } 3704 3705 restore = disks.MockMountPointDisksToPartitionMapping( 3706 map[disks.Mountpoint]*disks.MockDiskMapping{ 3707 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultEncDiskNoBoot, 3708 // no ubuntu-boot so we fall back to unlocking data with fallback 3709 // key right away 3710 { 3711 Mountpoint: boot.InitramfsHostUbuntuDataDir, 3712 IsDecryptedDevice: true, 3713 }: defaultEncDiskNoBoot, 3714 { 3715 Mountpoint: boot.InitramfsUbuntuSaveDir, 3716 IsDecryptedDevice: true, 3717 }: defaultEncDiskNoBoot, 3718 }, 3719 ) 3720 defer restore() 3721 3722 dataActivated := false 3723 unlockVolumeWithSealedKeyCalls := 0 3724 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 3725 unlockVolumeWithSealedKeyCalls++ 3726 switch unlockVolumeWithSealedKeyCalls { 3727 case 1: 3728 // we skip trying to unlock with run key on ubuntu-boot and go 3729 // directly to using the fallback key on ubuntu-seed 3730 c.Assert(name, Equals, "ubuntu-data") 3731 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key")) 3732 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 3733 c.Assert(err, IsNil) 3734 c.Assert(encDevPartUUID, Equals, "ubuntu-data-enc-partuuid") 3735 c.Assert(opts.AllowRecoveryKey, Equals, true) 3736 c.Assert(opts.WhichModel, NotNil) 3737 dataActivated = true 3738 // it was unlocked with a recovery key 3739 3740 return happyUnlocked("ubuntu-data", secboot.UnlockedWithRecoveryKey), nil 3741 default: 3742 c.Errorf("unexpected call to UnlockVolumeUsingSealedKeyIfEncrypted (num %d)", unlockVolumeWithSealedKeyCalls) 3743 return secboot.UnlockResult{}, fmt.Errorf("broken test") 3744 } 3745 }) 3746 defer restore() 3747 3748 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsHostWritableDir, "foo", "marker") 3749 s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker") 3750 3751 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 3752 c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not activated yet")) 3753 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 3754 c.Assert(err, IsNil) 3755 c.Assert(encDevPartUUID, Equals, "ubuntu-save-enc-partuuid") 3756 c.Assert(key, DeepEquals, []byte("foo")) 3757 return happyUnlocked("ubuntu-save", secboot.UnlockedWithKey), nil 3758 }) 3759 defer restore() 3760 3761 measureEpochCalls := 0 3762 measureModelCalls := 0 3763 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 3764 measureEpochCalls++ 3765 return nil 3766 }) 3767 defer restore() 3768 3769 var measuredModel *asserts.Model 3770 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 3771 measureModelCalls++ 3772 var err error 3773 measuredModel, err = findModel() 3774 if err != nil { 3775 return err 3776 } 3777 return nil 3778 }) 3779 defer restore() 3780 3781 restore = s.mockSystemdMountSequence(c, []systemdMount{ 3782 ubuntuLabelMount("ubuntu-seed", "recover"), 3783 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 3784 s.makeSeedSnapSystemdMount(snap.TypeKernel), 3785 s.makeSeedSnapSystemdMount(snap.TypeBase), 3786 { 3787 "tmpfs", 3788 boot.InitramfsDataDir, 3789 tmpfsMountOpts, 3790 }, 3791 // no ubuntu-boot 3792 { 3793 "/dev/mapper/ubuntu-data-random", 3794 boot.InitramfsHostUbuntuDataDir, 3795 needsNoSuidDiskMountOpts, 3796 }, 3797 { 3798 "/dev/mapper/ubuntu-save-random", 3799 boot.InitramfsUbuntuSaveDir, 3800 nil, 3801 }, 3802 }, nil) 3803 defer restore() 3804 3805 s.testRecoverModeHappy(c) 3806 3807 checkDegradedJSON(c, map[string]interface{}{ 3808 "ubuntu-boot": map[string]interface{}{ 3809 "find-state": "not-found", 3810 }, 3811 "ubuntu-data": map[string]interface{}{ 3812 "device": "/dev/mapper/ubuntu-data-random", 3813 "unlock-state": "unlocked", 3814 "find-state": "found", 3815 "mount-state": "mounted", 3816 "unlock-key": "recovery", 3817 "mount-location": boot.InitramfsHostUbuntuDataDir, 3818 }, 3819 "ubuntu-save": map[string]interface{}{ 3820 "device": "/dev/mapper/ubuntu-save-random", 3821 "unlock-key": "run", 3822 "unlock-state": "unlocked", 3823 "mount-state": "mounted", 3824 "find-state": "found", 3825 "mount-location": boot.InitramfsUbuntuSaveDir, 3826 }, 3827 "error-log": []interface{}{ 3828 "cannot find ubuntu-boot partition on disk defaultEncDevNoBoot", 3829 }, 3830 }) 3831 3832 c.Check(dataActivated, Equals, true) 3833 c.Check(unlockVolumeWithSealedKeyCalls, Equals, 1) 3834 c.Check(measureEpochCalls, Equals, 1) 3835 c.Check(measureModelCalls, Equals, 1) 3836 c.Check(measuredModel, DeepEquals, s.model) 3837 3838 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 3839 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent) 3840 } 3841 3842 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedDataUnlockFailSaveUnlockFallbackHappy(c *C) { 3843 // test a scenario when unsealing of data fails with both the run key 3844 // and fallback key, but save can be unlocked using the fallback key 3845 3846 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 3847 3848 restore := main.MockPartitionUUIDForBootedKernelDisk("") 3849 defer restore() 3850 3851 // setup a bootloader for setting the bootenv after we are done 3852 bloader := bootloadertest.Mock("mock", c.MkDir()) 3853 bootloader.Force(bloader) 3854 defer bootloader.Force(nil) 3855 3856 restore = disks.MockMountPointDisksToPartitionMapping( 3857 map[disks.Mountpoint]*disks.MockDiskMapping{ 3858 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultEncBootDisk, 3859 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultEncBootDisk, 3860 { 3861 Mountpoint: boot.InitramfsUbuntuSaveDir, 3862 IsDecryptedDevice: true, 3863 }: defaultEncBootDisk, 3864 }, 3865 ) 3866 defer restore() 3867 3868 dataActivationAttempts := 0 3869 saveActivated := false 3870 unlockVolumeWithSealedKeyCalls := 0 3871 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 3872 unlockVolumeWithSealedKeyCalls++ 3873 switch unlockVolumeWithSealedKeyCalls { 3874 3875 case 1: 3876 // ubuntu data can't be unlocked with run key 3877 c.Assert(name, Equals, "ubuntu-data") 3878 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")) 3879 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 3880 c.Assert(err, IsNil) 3881 c.Assert(encDevPartUUID, Equals, "ubuntu-data-enc-partuuid") 3882 c.Assert(opts.AllowRecoveryKey, Equals, false) 3883 c.Assert(opts.WhichModel, NotNil) 3884 dataActivationAttempts++ 3885 return foundEncrypted("ubuntu-data"), fmt.Errorf("failed to unlock ubuntu-data with run object") 3886 3887 case 2: 3888 // nor can it be unlocked with fallback key 3889 c.Assert(name, Equals, "ubuntu-data") 3890 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key")) 3891 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 3892 c.Assert(err, IsNil) 3893 c.Assert(encDevPartUUID, Equals, "ubuntu-data-enc-partuuid") 3894 c.Assert(opts.AllowRecoveryKey, Equals, true) 3895 c.Assert(opts.WhichModel, NotNil) 3896 dataActivationAttempts++ 3897 return foundEncrypted("ubuntu-data"), fmt.Errorf("failed to unlock ubuntu-data with fallback object") 3898 3899 case 3: 3900 // we can however still unlock ubuntu-save (somehow?) 3901 c.Assert(name, Equals, "ubuntu-save") 3902 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key")) 3903 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 3904 c.Assert(err, IsNil) 3905 c.Assert(encDevPartUUID, Equals, "ubuntu-save-enc-partuuid") 3906 c.Assert(opts.AllowRecoveryKey, Equals, true) 3907 c.Assert(opts.WhichModel, NotNil) 3908 saveActivated = true 3909 return happyUnlocked("ubuntu-save", secboot.UnlockedWithSealedKey), nil 3910 default: 3911 c.Errorf("unexpected call to UnlockVolumeUsingSealedKeyIfEncrypted (num %d)", unlockVolumeWithSealedKeyCalls) 3912 return secboot.UnlockResult{}, fmt.Errorf("broken test") 3913 } 3914 }) 3915 defer restore() 3916 3917 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsHostWritableDir, "foo", "") 3918 3919 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 3920 // nothing can call this function in the tested scenario 3921 c.Fatalf("unexpected call") 3922 return secboot.UnlockResult{}, fmt.Errorf("unexpected call") 3923 }) 3924 defer restore() 3925 3926 measureEpochCalls := 0 3927 measureModelCalls := 0 3928 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 3929 measureEpochCalls++ 3930 return nil 3931 }) 3932 defer restore() 3933 3934 var measuredModel *asserts.Model 3935 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 3936 measureModelCalls++ 3937 var err error 3938 measuredModel, err = findModel() 3939 if err != nil { 3940 return err 3941 } 3942 return nil 3943 }) 3944 defer restore() 3945 3946 restore = s.mockSystemdMountSequence(c, []systemdMount{ 3947 ubuntuLabelMount("ubuntu-seed", "recover"), 3948 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 3949 s.makeSeedSnapSystemdMount(snap.TypeKernel), 3950 s.makeSeedSnapSystemdMount(snap.TypeBase), 3951 { 3952 "tmpfs", 3953 boot.InitramfsDataDir, 3954 tmpfsMountOpts, 3955 }, 3956 { 3957 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 3958 boot.InitramfsUbuntuBootDir, 3959 needsFsckDiskMountOpts, 3960 }, 3961 { 3962 "/dev/mapper/ubuntu-save-random", 3963 boot.InitramfsUbuntuSaveDir, 3964 nil, 3965 }, 3966 }, nil) 3967 defer restore() 3968 3969 // ensure that we check that access to sealed keys were locked 3970 sealedKeysLocked := false 3971 restore = main.MockSecbootLockSealedKeys(func() error { 3972 sealedKeysLocked = true 3973 return nil 3974 }) 3975 defer restore() 3976 3977 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 3978 c.Assert(err, IsNil) 3979 3980 // we always need to lock access to sealed keys 3981 c.Check(sealedKeysLocked, Equals, true) 3982 3983 modeEnv := filepath.Join(boot.InitramfsWritableDir, "var/lib/snapd/modeenv") 3984 c.Check(modeEnv, testutil.FileEquals, `mode=recover 3985 recovery_system=20191118 3986 base=core20_1.snap 3987 model=my-brand/my-model 3988 grade=signed 3989 `) 3990 3991 checkDegradedJSON(c, map[string]interface{}{ 3992 "ubuntu-boot": map[string]interface{}{ 3993 "device": "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 3994 "mount-state": "mounted", 3995 "find-state": "found", 3996 "mount-location": boot.InitramfsUbuntuBootDir, 3997 }, 3998 "ubuntu-data": map[string]interface{}{ 3999 "find-state": "found", 4000 "device": "/dev/disk/by-partuuid/ubuntu-data-enc-partuuid", 4001 "unlock-state": "error-unlocking", 4002 }, 4003 "ubuntu-save": map[string]interface{}{ 4004 "device": "/dev/mapper/ubuntu-save-random", 4005 "unlock-key": "fallback", 4006 "unlock-state": "unlocked", 4007 "mount-state": "mounted", 4008 "find-state": "found", 4009 "mount-location": boot.InitramfsUbuntuSaveDir, 4010 }, 4011 "error-log": []interface{}{ 4012 "cannot unlock encrypted ubuntu-data (device /dev/disk/by-partuuid/ubuntu-data-enc-partuuid) with sealed run key: failed to unlock ubuntu-data with run object", 4013 "cannot unlock encrypted ubuntu-data partition with sealed fallback key: failed to unlock ubuntu-data with fallback object", 4014 }, 4015 }) 4016 4017 bloader2, err := bootloader.Find("", nil) 4018 c.Assert(err, IsNil) 4019 m, err := bloader2.GetBootVars("snapd_recovery_system", "snapd_recovery_mode") 4020 c.Assert(err, IsNil) 4021 c.Assert(m, DeepEquals, map[string]string{ 4022 "snapd_recovery_system": "20191118", 4023 "snapd_recovery_mode": "run", 4024 }) 4025 4026 // since we didn't mount data at all, we won't have copied in files from 4027 // there and instead will copy safe defaults to the ephemeral data 4028 c.Assert(filepath.Join(boot.InitramfsRunMntDir, "/data/system-data/var/lib/console-conf/complete"), testutil.FilePresent) 4029 4030 c.Check(dataActivationAttempts, Equals, 2) 4031 c.Check(saveActivated, Equals, true) 4032 c.Check(unlockVolumeWithSealedKeyCalls, Equals, 3) 4033 c.Check(measureEpochCalls, Equals, 1) 4034 c.Check(measureModelCalls, Equals, 1) 4035 c.Check(measuredModel, DeepEquals, s.model) 4036 4037 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 4038 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent) 4039 } 4040 4041 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeDegradedAbsentDataUnencryptedSaveHappy(c *C) { 4042 // test a scenario when data cannot be found but unencrypted save can be 4043 // mounted 4044 4045 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 4046 4047 restore := main.MockPartitionUUIDForBootedKernelDisk("") 4048 defer restore() 4049 4050 // setup a bootloader for setting the bootenv after we are done 4051 bloader := bootloadertest.Mock("mock", c.MkDir()) 4052 bootloader.Force(bloader) 4053 defer bootloader.Force(nil) 4054 4055 // no ubuntu-data on the disk at all 4056 mockDiskNoData := &disks.MockDiskMapping{ 4057 FilesystemLabelToPartUUID: map[string]string{ 4058 "ubuntu-boot": "ubuntu-boot-partuuid", 4059 "ubuntu-seed": "ubuntu-seed-partuuid", 4060 "ubuntu-save": "ubuntu-save-partuuid", 4061 }, 4062 DiskHasPartitions: true, 4063 DevNum: "noDataUnenc", 4064 } 4065 4066 restore = disks.MockMountPointDisksToPartitionMapping( 4067 map[disks.Mountpoint]*disks.MockDiskMapping{ 4068 {Mountpoint: boot.InitramfsUbuntuSeedDir}: mockDiskNoData, 4069 {Mountpoint: boot.InitramfsUbuntuBootDir}: mockDiskNoData, 4070 {Mountpoint: boot.InitramfsUbuntuSaveDir}: mockDiskNoData, 4071 }, 4072 ) 4073 defer restore() 4074 4075 dataActivated := false 4076 unlockVolumeWithSealedKeyCalls := 0 4077 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 4078 unlockVolumeWithSealedKeyCalls++ 4079 switch unlockVolumeWithSealedKeyCalls { 4080 4081 case 1: 4082 // ubuntu data can't be found at all 4083 c.Assert(name, Equals, "ubuntu-data") 4084 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")) 4085 _, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 4086 c.Assert(err, FitsTypeOf, disks.PartitionNotFoundError{}) 4087 c.Assert(opts.AllowRecoveryKey, Equals, false) 4088 c.Assert(opts.WhichModel, NotNil) 4089 // sanity check that we can't find a normal ubuntu-data either 4090 _, err = disk.FindMatchingPartitionUUIDWithFsLabel(name) 4091 c.Assert(err, FitsTypeOf, disks.PartitionNotFoundError{}) 4092 dataActivated = true 4093 // data not found at all 4094 return notFoundPart(), fmt.Errorf("error enumerating to find ubuntu-data") 4095 default: 4096 c.Errorf("unexpected call to UnlockVolumeUsingSealedKeyIfEncrypted (num %d)", unlockVolumeWithSealedKeyCalls) 4097 return secboot.UnlockResult{}, fmt.Errorf("broken test") 4098 } 4099 }) 4100 defer restore() 4101 4102 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsHostWritableDir, "foo", "") 4103 4104 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 4105 // nothing can call this function in the tested scenario 4106 c.Fatalf("unexpected call") 4107 return secboot.UnlockResult{}, fmt.Errorf("unexpected call") 4108 }) 4109 defer restore() 4110 4111 measureEpochCalls := 0 4112 measureModelCalls := 0 4113 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 4114 measureEpochCalls++ 4115 return nil 4116 }) 4117 defer restore() 4118 4119 var measuredModel *asserts.Model 4120 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 4121 measureModelCalls++ 4122 var err error 4123 measuredModel, err = findModel() 4124 if err != nil { 4125 return err 4126 } 4127 return nil 4128 }) 4129 defer restore() 4130 4131 restore = s.mockSystemdMountSequence(c, []systemdMount{ 4132 ubuntuLabelMount("ubuntu-seed", "recover"), 4133 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 4134 s.makeSeedSnapSystemdMount(snap.TypeKernel), 4135 s.makeSeedSnapSystemdMount(snap.TypeBase), 4136 { 4137 "tmpfs", 4138 boot.InitramfsDataDir, 4139 tmpfsMountOpts, 4140 }, 4141 { 4142 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 4143 boot.InitramfsUbuntuBootDir, 4144 needsFsckDiskMountOpts, 4145 }, 4146 { 4147 "/dev/disk/by-partuuid/ubuntu-save-partuuid", 4148 boot.InitramfsUbuntuSaveDir, 4149 nil, 4150 }, 4151 }, nil) 4152 defer restore() 4153 4154 // ensure that we check that access to sealed keys were locked 4155 sealedKeysLocked := false 4156 restore = main.MockSecbootLockSealedKeys(func() error { 4157 sealedKeysLocked = true 4158 return nil 4159 }) 4160 defer restore() 4161 4162 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 4163 c.Assert(err, IsNil) 4164 4165 // we always need to lock access to sealed keys 4166 c.Check(sealedKeysLocked, Equals, true) 4167 4168 modeEnv := filepath.Join(boot.InitramfsWritableDir, "var/lib/snapd/modeenv") 4169 c.Check(modeEnv, testutil.FileEquals, `mode=recover 4170 recovery_system=20191118 4171 base=core20_1.snap 4172 model=my-brand/my-model 4173 grade=signed 4174 `) 4175 4176 checkDegradedJSON(c, map[string]interface{}{ 4177 "ubuntu-boot": map[string]interface{}{ 4178 "device": "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 4179 "mount-state": "mounted", 4180 "find-state": "found", 4181 "mount-location": boot.InitramfsUbuntuBootDir, 4182 }, 4183 "ubuntu-data": map[string]interface{}{ 4184 "find-state": "not-found", 4185 }, 4186 "ubuntu-save": map[string]interface{}{ 4187 "device": "/dev/disk/by-partuuid/ubuntu-save-partuuid", 4188 "mount-state": "mounted", 4189 "find-state": "found", 4190 "mount-location": boot.InitramfsUbuntuSaveDir, 4191 }, 4192 "error-log": []interface{}{ 4193 "cannot locate ubuntu-data partition for mounting host data: error enumerating to find ubuntu-data", 4194 }, 4195 }) 4196 4197 bloader2, err := bootloader.Find("", nil) 4198 c.Assert(err, IsNil) 4199 m, err := bloader2.GetBootVars("snapd_recovery_system", "snapd_recovery_mode") 4200 c.Assert(err, IsNil) 4201 c.Assert(m, DeepEquals, map[string]string{ 4202 "snapd_recovery_system": "20191118", 4203 "snapd_recovery_mode": "run", 4204 }) 4205 4206 // since we didn't mount data at all, we won't have copied in files from 4207 // there and instead will copy safe defaults to the ephemeral data 4208 c.Assert(filepath.Join(boot.InitramfsRunMntDir, "/data/system-data/var/lib/console-conf/complete"), testutil.FilePresent) 4209 4210 c.Check(dataActivated, Equals, true) 4211 // unlocked tried only once, when attempting to set up ubuntu-data 4212 c.Check(unlockVolumeWithSealedKeyCalls, Equals, 1) 4213 c.Check(measureEpochCalls, Equals, 1) 4214 c.Check(measureModelCalls, Equals, 1) 4215 c.Check(measuredModel, DeepEquals, s.model) 4216 4217 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 4218 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent) 4219 } 4220 4221 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeDegradedUnencryptedDataSaveEncryptedHappy(c *C) { 4222 // test a rather impossible scenario when data is unencrypted, but save 4223 // is encrypted and thus gets completely ignored, because plain data 4224 // implies plain save 4225 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 4226 4227 restore := main.MockPartitionUUIDForBootedKernelDisk("") 4228 defer restore() 4229 4230 // setup a bootloader for setting the bootenv after we are done 4231 bloader := bootloadertest.Mock("mock", c.MkDir()) 4232 bootloader.Force(bloader) 4233 defer bootloader.Force(nil) 4234 4235 // no ubuntu-data on the disk at all 4236 mockDiskDataUnencSaveEnc := &disks.MockDiskMapping{ 4237 FilesystemLabelToPartUUID: map[string]string{ 4238 "ubuntu-boot": "ubuntu-boot-partuuid", 4239 "ubuntu-seed": "ubuntu-seed-partuuid", 4240 // ubuntu-data is unencrypted but ubuntu-save is encrypted 4241 "ubuntu-data": "ubuntu-data-partuuid", 4242 "ubuntu-save-enc": "ubuntu-save-enc-partuuid", 4243 }, 4244 DiskHasPartitions: true, 4245 DevNum: "dataUnencSaveEnc", 4246 } 4247 4248 restore = disks.MockMountPointDisksToPartitionMapping( 4249 map[disks.Mountpoint]*disks.MockDiskMapping{ 4250 {Mountpoint: boot.InitramfsUbuntuSeedDir}: mockDiskDataUnencSaveEnc, 4251 {Mountpoint: boot.InitramfsUbuntuBootDir}: mockDiskDataUnencSaveEnc, 4252 {Mountpoint: boot.InitramfsHostUbuntuDataDir}: mockDiskDataUnencSaveEnc, 4253 // we don't include the mountpoint for ubuntu-save, since it should 4254 // never be mounted 4255 }, 4256 ) 4257 defer restore() 4258 4259 dataActivated := false 4260 unlockVolumeWithSealedKeyCalls := 0 4261 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 4262 unlockVolumeWithSealedKeyCalls++ 4263 switch unlockVolumeWithSealedKeyCalls { 4264 4265 case 1: 4266 // ubuntu data is a plain old unencrypted partition 4267 c.Assert(name, Equals, "ubuntu-data") 4268 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")) 4269 _, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 4270 c.Assert(err, FitsTypeOf, disks.PartitionNotFoundError{}) 4271 c.Assert(opts.AllowRecoveryKey, Equals, false) 4272 c.Assert(opts.WhichModel, NotNil) 4273 // sanity check that we can't find a normal ubuntu-data either 4274 partUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name) 4275 c.Assert(err, IsNil) 4276 c.Assert(partUUID, Equals, "ubuntu-data-partuuid") 4277 dataActivated = true 4278 4279 return foundUnencrypted("ubuntu-data"), nil 4280 default: 4281 // no other partition is activated via secboot calls 4282 c.Errorf("unexpected call to UnlockVolumeUsingSealedKeyIfEncrypted (num %d)", unlockVolumeWithSealedKeyCalls) 4283 return secboot.UnlockResult{}, fmt.Errorf("broken test") 4284 } 4285 }) 4286 defer restore() 4287 4288 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsHostWritableDir, "foo", "") 4289 4290 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 4291 // nothing can call this function in the tested scenario 4292 c.Fatalf("unexpected call") 4293 return secboot.UnlockResult{}, fmt.Errorf("unexpected call") 4294 }) 4295 defer restore() 4296 4297 measureEpochCalls := 0 4298 measureModelCalls := 0 4299 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 4300 measureEpochCalls++ 4301 return nil 4302 }) 4303 defer restore() 4304 4305 var measuredModel *asserts.Model 4306 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 4307 measureModelCalls++ 4308 var err error 4309 measuredModel, err = findModel() 4310 if err != nil { 4311 return err 4312 } 4313 return nil 4314 }) 4315 defer restore() 4316 4317 restore = s.mockSystemdMountSequence(c, []systemdMount{ 4318 ubuntuLabelMount("ubuntu-seed", "recover"), 4319 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 4320 s.makeSeedSnapSystemdMount(snap.TypeKernel), 4321 s.makeSeedSnapSystemdMount(snap.TypeBase), 4322 { 4323 "tmpfs", 4324 boot.InitramfsDataDir, 4325 tmpfsMountOpts, 4326 }, 4327 { 4328 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 4329 boot.InitramfsUbuntuBootDir, 4330 needsFsckDiskMountOpts, 4331 }, 4332 { 4333 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 4334 boot.InitramfsHostUbuntuDataDir, 4335 needsNoSuidDiskMountOpts, 4336 }, 4337 }, nil) 4338 defer restore() 4339 4340 s.testRecoverModeHappy(c) 4341 4342 c.Check(dataActivated, Equals, true) 4343 c.Check(unlockVolumeWithSealedKeyCalls, Equals, 1) 4344 c.Check(measureEpochCalls, Equals, 1) 4345 c.Check(measureModelCalls, Equals, 1) 4346 c.Check(measuredModel, DeepEquals, s.model) 4347 4348 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 4349 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent) 4350 4351 // the system is not encrypted, even if encrypted save exists it gets 4352 // ignored 4353 c.Check(s.logs.String(), testutil.Contains, "ignoring unexpected encrypted ubuntu-save") 4354 } 4355 4356 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeDegradedEncryptedDataUnencryptedSaveHappy(c *C) { 4357 // test a scenario when data is encrypted, thus implying an encrypted 4358 // ubuntu save, but save found on the disk is unencrypted 4359 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 4360 4361 restore := main.MockPartitionUUIDForBootedKernelDisk("") 4362 defer restore() 4363 4364 // setup a bootloader for setting the bootenv after we are done 4365 bloader := bootloadertest.Mock("mock", c.MkDir()) 4366 bootloader.Force(bloader) 4367 defer bootloader.Force(nil) 4368 4369 mockDiskDataUnencSaveEnc := &disks.MockDiskMapping{ 4370 FilesystemLabelToPartUUID: map[string]string{ 4371 "ubuntu-boot": "ubuntu-boot-partuuid", 4372 "ubuntu-seed": "ubuntu-seed-partuuid", 4373 "ubuntu-data-enc": "ubuntu-data-enc-partuuid", 4374 // ubuntu-data is encrypted but ubuntu-save is not 4375 "ubuntu-save": "ubuntu-save-partuuid", 4376 }, 4377 DiskHasPartitions: true, 4378 DevNum: "dataUnencSaveEnc", 4379 } 4380 4381 restore = disks.MockMountPointDisksToPartitionMapping( 4382 map[disks.Mountpoint]*disks.MockDiskMapping{ 4383 {Mountpoint: boot.InitramfsUbuntuSeedDir}: mockDiskDataUnencSaveEnc, 4384 {Mountpoint: boot.InitramfsUbuntuBootDir}: mockDiskDataUnencSaveEnc, 4385 {Mountpoint: boot.InitramfsHostUbuntuDataDir}: mockDiskDataUnencSaveEnc, 4386 // we don't include the mountpoint for ubuntu-save, since it should 4387 // never be mounted - we fail as soon as we find the encrypted save 4388 // and unlock it, but before we mount it 4389 }, 4390 ) 4391 defer restore() 4392 4393 unlockVolumeWithSealedKeyCalls := 0 4394 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 4395 unlockVolumeWithSealedKeyCalls++ 4396 switch unlockVolumeWithSealedKeyCalls { 4397 4398 case 1: 4399 // ubuntu-data is encrypted partition 4400 c.Assert(name, Equals, "ubuntu-data") 4401 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")) 4402 _, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 4403 c.Assert(err, IsNil) 4404 // sanity check that we can't find a normal ubuntu-data either 4405 _, err = disk.FindMatchingPartitionUUIDWithFsLabel(name) 4406 c.Assert(err, FitsTypeOf, disks.PartitionNotFoundError{}) 4407 return foundEncrypted("ubuntu-data"), fmt.Errorf("failed to unlock ubuntu-data with run object") 4408 case 2: 4409 c.Assert(name, Equals, "ubuntu-data") 4410 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key")) 4411 return foundEncrypted("ubuntu-data"), fmt.Errorf("failed to unlock ubuntu-data with recovery object") 4412 case 3: 4413 // we are asked to unlock encrypted ubuntu-save with the recovery key 4414 c.Assert(name, Equals, "ubuntu-save") 4415 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key")) 4416 _, err := disk.FindMatchingPartitionUUIDWithFsLabel(name) 4417 c.Assert(err, IsNil) 4418 _, err = disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 4419 // sanity 4420 c.Assert(err, FitsTypeOf, disks.PartitionNotFoundError{}) 4421 // but we find an unencrypted one instead 4422 return foundUnencrypted("ubuntu-save"), nil 4423 default: 4424 c.Errorf("unexpected call to UnlockVolumeUsingSealedKeyIfEncrypted (num %d)", unlockVolumeWithSealedKeyCalls) 4425 return secboot.UnlockResult{}, fmt.Errorf("broken test") 4426 } 4427 }) 4428 defer restore() 4429 4430 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsHostWritableDir, "foo", "") 4431 4432 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 4433 // nothing can call this function in the tested scenario 4434 c.Fatalf("unexpected call") 4435 return secboot.UnlockResult{}, fmt.Errorf("unexpected call") 4436 }) 4437 defer restore() 4438 4439 measureEpochCalls := 0 4440 measureModelCalls := 0 4441 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 4442 measureEpochCalls++ 4443 return nil 4444 }) 4445 defer restore() 4446 4447 var measuredModel *asserts.Model 4448 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 4449 measureModelCalls++ 4450 var err error 4451 measuredModel, err = findModel() 4452 if err != nil { 4453 return err 4454 } 4455 return nil 4456 }) 4457 defer restore() 4458 4459 restore = s.mockSystemdMountSequence(c, []systemdMount{ 4460 ubuntuLabelMount("ubuntu-seed", "recover"), 4461 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 4462 s.makeSeedSnapSystemdMount(snap.TypeKernel), 4463 s.makeSeedSnapSystemdMount(snap.TypeBase), 4464 { 4465 "tmpfs", 4466 boot.InitramfsDataDir, 4467 tmpfsMountOpts, 4468 }, 4469 { 4470 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 4471 boot.InitramfsUbuntuBootDir, 4472 needsFsckDiskMountOpts, 4473 }, 4474 }, nil) 4475 defer restore() 4476 4477 // ensure that we check that access to sealed keys were locked 4478 sealedKeysLocked := false 4479 restore = main.MockSecbootLockSealedKeys(func() error { 4480 sealedKeysLocked = true 4481 return nil 4482 }) 4483 defer restore() 4484 4485 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 4486 c.Assert(err, ErrorMatches, `inconsistent disk encryption status: previous access resulted in encrypted, but now is unencrypted from partition ubuntu-save`) 4487 4488 // we always need to lock access to sealed keys 4489 c.Check(sealedKeysLocked, Equals, true) 4490 4491 // unlocking tried 3 times, first attempt tries to unlock ubuntu-data 4492 // with run key, then the recovery key, and lastly we tried to unlock 4493 // ubuntu-save with the recovery key 4494 c.Check(unlockVolumeWithSealedKeyCalls, Equals, 3) 4495 c.Check(measureEpochCalls, Equals, 1) 4496 c.Check(measureModelCalls, Equals, 1) 4497 c.Check(measuredModel, DeepEquals, s.model) 4498 4499 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 4500 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent) 4501 } 4502 4503 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeUnencryptedDataUnencryptedSaveHappy(c *C) { 4504 // test a scenario when data is unencrypted, same goes for save and the 4505 // test observes calls to secboot unlock helper 4506 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 4507 4508 restore := main.MockPartitionUUIDForBootedKernelDisk("") 4509 defer restore() 4510 4511 // setup a bootloader for setting the bootenv after we are done 4512 bloader := bootloadertest.Mock("mock", c.MkDir()) 4513 bootloader.Force(bloader) 4514 defer bootloader.Force(nil) 4515 4516 restore = disks.MockMountPointDisksToPartitionMapping( 4517 map[disks.Mountpoint]*disks.MockDiskMapping{ 4518 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultBootWithSaveDisk, 4519 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootWithSaveDisk, 4520 {Mountpoint: boot.InitramfsHostUbuntuDataDir}: defaultBootWithSaveDisk, 4521 {Mountpoint: boot.InitramfsUbuntuSaveDir}: defaultBootWithSaveDisk, 4522 }, 4523 ) 4524 defer restore() 4525 4526 unlockVolumeWithSealedKeyCalls := 0 4527 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 4528 unlockVolumeWithSealedKeyCalls++ 4529 switch unlockVolumeWithSealedKeyCalls { 4530 4531 case 1: 4532 // ubuntu data is an unencrypted partition 4533 c.Assert(name, Equals, "ubuntu-data") 4534 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")) 4535 _, err := disk.FindMatchingPartitionUUIDWithFsLabel(name) 4536 c.Assert(err, IsNil) 4537 // sanity check that we can't find encrypted ubuntu-data 4538 _, err = disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 4539 c.Assert(err, FitsTypeOf, disks.PartitionNotFoundError{}) 4540 return foundUnencrypted("ubuntu-data"), nil 4541 default: 4542 // we do not expect any more calls here, since 4543 // ubuntu-data was found unencrypted unlocking will not 4544 // be tried again 4545 c.Errorf("unexpected call to UnlockVolumeUsingSealedKeyIfEncrypted (num %d)", unlockVolumeWithSealedKeyCalls) 4546 return secboot.UnlockResult{}, fmt.Errorf("broken test") 4547 } 4548 }) 4549 defer restore() 4550 4551 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsHostWritableDir, "foo", "") 4552 4553 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 4554 // nothing can call this function in the tested scenario 4555 c.Fatalf("unexpected call") 4556 return secboot.UnlockResult{}, fmt.Errorf("unexpected call") 4557 }) 4558 defer restore() 4559 4560 measureEpochCalls := 0 4561 measureModelCalls := 0 4562 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 4563 measureEpochCalls++ 4564 return nil 4565 }) 4566 defer restore() 4567 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 4568 measureModelCalls++ 4569 return nil 4570 }) 4571 defer restore() 4572 4573 restore = s.mockSystemdMountSequence(c, []systemdMount{ 4574 ubuntuLabelMount("ubuntu-seed", "recover"), 4575 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 4576 s.makeSeedSnapSystemdMount(snap.TypeKernel), 4577 s.makeSeedSnapSystemdMount(snap.TypeBase), 4578 { 4579 "tmpfs", 4580 boot.InitramfsDataDir, 4581 tmpfsMountOpts, 4582 }, 4583 { 4584 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 4585 boot.InitramfsUbuntuBootDir, 4586 needsFsckDiskMountOpts, 4587 }, 4588 { 4589 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 4590 boot.InitramfsHostUbuntuDataDir, 4591 needsNoSuidDiskMountOpts, 4592 }, 4593 { 4594 "/dev/disk/by-partuuid/ubuntu-save-partuuid", 4595 boot.InitramfsUbuntuSaveDir, 4596 nil, 4597 }, 4598 }, nil) 4599 defer restore() 4600 4601 s.testRecoverModeHappy(c) 4602 4603 c.Check(unlockVolumeWithSealedKeyCalls, Equals, 1) 4604 c.Check(measureEpochCalls, Equals, 1) 4605 c.Check(measureModelCalls, Equals, 1) 4606 4607 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 4608 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent) 4609 } 4610 4611 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedAbsentDataSaveUnlockFallbackHappy(c *C) { 4612 // test a scenario when data cannot be found but save can be 4613 // unlocked using the fallback key 4614 4615 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 4616 4617 restore := main.MockPartitionUUIDForBootedKernelDisk("") 4618 defer restore() 4619 4620 // setup a bootloader for setting the bootenv after we are done 4621 bloader := bootloadertest.Mock("mock", c.MkDir()) 4622 bootloader.Force(bloader) 4623 defer bootloader.Force(nil) 4624 4625 // no ubuntu-data on the disk at all 4626 mockDiskNoData := &disks.MockDiskMapping{ 4627 FilesystemLabelToPartUUID: map[string]string{ 4628 "ubuntu-boot": "ubuntu-boot-partuuid", 4629 "ubuntu-seed": "ubuntu-seed-partuuid", 4630 "ubuntu-save-enc": "ubuntu-save-enc-partuuid", 4631 }, 4632 DiskHasPartitions: true, 4633 DevNum: "defaultEncDev", 4634 } 4635 4636 restore = disks.MockMountPointDisksToPartitionMapping( 4637 map[disks.Mountpoint]*disks.MockDiskMapping{ 4638 {Mountpoint: boot.InitramfsUbuntuSeedDir}: mockDiskNoData, 4639 {Mountpoint: boot.InitramfsUbuntuBootDir}: mockDiskNoData, 4640 { 4641 Mountpoint: boot.InitramfsUbuntuSaveDir, 4642 IsDecryptedDevice: true, 4643 }: mockDiskNoData, 4644 }, 4645 ) 4646 defer restore() 4647 4648 dataActivated := false 4649 saveActivated := false 4650 unlockVolumeWithSealedKeyCalls := 0 4651 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 4652 unlockVolumeWithSealedKeyCalls++ 4653 switch unlockVolumeWithSealedKeyCalls { 4654 4655 case 1: 4656 // ubuntu data can't be found at all 4657 c.Assert(name, Equals, "ubuntu-data") 4658 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")) 4659 _, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 4660 c.Assert(err, FitsTypeOf, disks.PartitionNotFoundError{}) 4661 c.Assert(opts.AllowRecoveryKey, Equals, false) 4662 c.Assert(opts.WhichModel, NotNil) 4663 dataActivated = true 4664 // data not found at all 4665 return notFoundPart(), fmt.Errorf("error enumerating to find ubuntu-data") 4666 4667 case 2: 4668 // we can however still unlock ubuntu-save with the fallback key 4669 c.Assert(name, Equals, "ubuntu-save") 4670 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key")) 4671 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 4672 c.Assert(err, IsNil) 4673 c.Assert(encDevPartUUID, Equals, "ubuntu-save-enc-partuuid") 4674 c.Assert(opts.AllowRecoveryKey, Equals, true) 4675 c.Assert(opts.WhichModel, NotNil) 4676 saveActivated = true 4677 return happyUnlocked("ubuntu-save", secboot.UnlockedWithSealedKey), nil 4678 default: 4679 c.Errorf("unexpected call to UnlockVolumeUsingSealedKeyIfEncrypted (num %d)", unlockVolumeWithSealedKeyCalls) 4680 return secboot.UnlockResult{}, fmt.Errorf("broken test") 4681 } 4682 }) 4683 defer restore() 4684 4685 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsHostWritableDir, "foo", "") 4686 4687 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 4688 // nothing can call this function in the tested scenario 4689 c.Fatalf("unexpected call") 4690 return secboot.UnlockResult{}, fmt.Errorf("unexpected call") 4691 }) 4692 defer restore() 4693 4694 measureEpochCalls := 0 4695 measureModelCalls := 0 4696 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 4697 measureEpochCalls++ 4698 return nil 4699 }) 4700 defer restore() 4701 4702 var measuredModel *asserts.Model 4703 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 4704 measureModelCalls++ 4705 var err error 4706 measuredModel, err = findModel() 4707 if err != nil { 4708 return err 4709 } 4710 return nil 4711 }) 4712 defer restore() 4713 4714 restore = s.mockSystemdMountSequence(c, []systemdMount{ 4715 ubuntuLabelMount("ubuntu-seed", "recover"), 4716 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 4717 s.makeSeedSnapSystemdMount(snap.TypeKernel), 4718 s.makeSeedSnapSystemdMount(snap.TypeBase), 4719 { 4720 "tmpfs", 4721 boot.InitramfsDataDir, 4722 tmpfsMountOpts, 4723 }, 4724 { 4725 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 4726 boot.InitramfsUbuntuBootDir, 4727 needsFsckDiskMountOpts, 4728 }, 4729 { 4730 "/dev/mapper/ubuntu-save-random", 4731 boot.InitramfsUbuntuSaveDir, 4732 nil, 4733 }, 4734 }, nil) 4735 defer restore() 4736 4737 // ensure that we check that access to sealed keys were locked 4738 sealedKeysLocked := false 4739 restore = main.MockSecbootLockSealedKeys(func() error { 4740 sealedKeysLocked = true 4741 return nil 4742 }) 4743 defer restore() 4744 4745 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 4746 c.Assert(err, IsNil) 4747 4748 // we always need to lock access to sealed keys 4749 c.Check(sealedKeysLocked, Equals, true) 4750 4751 modeEnv := filepath.Join(boot.InitramfsWritableDir, "var/lib/snapd/modeenv") 4752 c.Check(modeEnv, testutil.FileEquals, `mode=recover 4753 recovery_system=20191118 4754 base=core20_1.snap 4755 model=my-brand/my-model 4756 grade=signed 4757 `) 4758 4759 checkDegradedJSON(c, map[string]interface{}{ 4760 "ubuntu-boot": map[string]interface{}{ 4761 "device": "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 4762 "mount-state": "mounted", 4763 "find-state": "found", 4764 "mount-location": boot.InitramfsUbuntuBootDir, 4765 }, 4766 "ubuntu-data": map[string]interface{}{ 4767 "find-state": "not-found", 4768 }, 4769 "ubuntu-save": map[string]interface{}{ 4770 "device": "/dev/mapper/ubuntu-save-random", 4771 "unlock-key": "fallback", 4772 "unlock-state": "unlocked", 4773 "mount-state": "mounted", 4774 "find-state": "found", 4775 "mount-location": boot.InitramfsUbuntuSaveDir, 4776 }, 4777 "error-log": []interface{}{ 4778 "cannot locate ubuntu-data partition for mounting host data: error enumerating to find ubuntu-data", 4779 }, 4780 }) 4781 4782 bloader2, err := bootloader.Find("", nil) 4783 c.Assert(err, IsNil) 4784 m, err := bloader2.GetBootVars("snapd_recovery_system", "snapd_recovery_mode") 4785 c.Assert(err, IsNil) 4786 c.Assert(m, DeepEquals, map[string]string{ 4787 "snapd_recovery_system": "20191118", 4788 "snapd_recovery_mode": "run", 4789 }) 4790 4791 // since we didn't mount data at all, we won't have copied in files from 4792 // there and instead will copy safe defaults to the ephemeral data 4793 c.Assert(filepath.Join(boot.InitramfsRunMntDir, "/data/system-data/var/lib/console-conf/complete"), testutil.FilePresent) 4794 4795 c.Check(dataActivated, Equals, true) 4796 c.Check(saveActivated, Equals, true) 4797 c.Check(unlockVolumeWithSealedKeyCalls, Equals, 2) 4798 c.Check(measureEpochCalls, Equals, 1) 4799 c.Check(measureModelCalls, Equals, 1) 4800 c.Check(measuredModel, DeepEquals, s.model) 4801 4802 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 4803 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent) 4804 } 4805 4806 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedDataUnlockFailSaveUnlockFailHappy(c *C) { 4807 // test a scenario when unlocking data with both run and fallback keys 4808 // fails, followed by a failure to unlock save with the fallback key 4809 4810 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 4811 4812 restore := main.MockPartitionUUIDForBootedKernelDisk("") 4813 defer restore() 4814 4815 // setup a bootloader for setting the bootenv after we are done 4816 bloader := bootloadertest.Mock("mock", c.MkDir()) 4817 bootloader.Force(bloader) 4818 defer bootloader.Force(nil) 4819 4820 restore = disks.MockMountPointDisksToPartitionMapping( 4821 // no ubuntu-data mountpoint is mocked, but there is an 4822 // ubuntu-data-enc partition in the disk we find 4823 map[disks.Mountpoint]*disks.MockDiskMapping{ 4824 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultEncBootDisk, 4825 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultEncBootDisk, 4826 { 4827 Mountpoint: boot.InitramfsUbuntuSaveDir, 4828 IsDecryptedDevice: true, 4829 }: defaultEncBootDisk, 4830 }, 4831 ) 4832 defer restore() 4833 4834 dataActivationAttempts := 0 4835 saveUnsealActivationAttempted := false 4836 unlockVolumeWithSealedKeyCalls := 0 4837 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 4838 unlockVolumeWithSealedKeyCalls++ 4839 switch unlockVolumeWithSealedKeyCalls { 4840 4841 case 1: 4842 // ubuntu data can't be unlocked with run key 4843 c.Assert(name, Equals, "ubuntu-data") 4844 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")) 4845 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 4846 c.Assert(err, IsNil) 4847 c.Assert(encDevPartUUID, Equals, "ubuntu-data-enc-partuuid") 4848 c.Assert(opts.AllowRecoveryKey, Equals, false) 4849 c.Assert(opts.WhichModel, NotNil) 4850 dataActivationAttempts++ 4851 return foundEncrypted("ubuntu-data"), fmt.Errorf("failed to unlock ubuntu-data with run object") 4852 4853 case 2: 4854 // nor can it be unlocked with fallback key 4855 c.Assert(name, Equals, "ubuntu-data") 4856 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key")) 4857 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 4858 c.Assert(err, IsNil) 4859 c.Assert(encDevPartUUID, Equals, "ubuntu-data-enc-partuuid") 4860 c.Assert(opts.AllowRecoveryKey, Equals, true) 4861 c.Assert(opts.WhichModel, NotNil) 4862 dataActivationAttempts++ 4863 return foundEncrypted("ubuntu-data"), fmt.Errorf("failed to unlock ubuntu-data with fallback object") 4864 4865 case 3: 4866 // we also fail to unlock save 4867 4868 // no attempts to activate ubuntu-save yet 4869 c.Assert(name, Equals, "ubuntu-save") 4870 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key")) 4871 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 4872 c.Assert(err, IsNil) 4873 c.Assert(encDevPartUUID, Equals, "ubuntu-save-enc-partuuid") 4874 c.Assert(opts.AllowRecoveryKey, Equals, true) 4875 c.Assert(opts.WhichModel, NotNil) 4876 saveUnsealActivationAttempted = true 4877 return foundEncrypted("ubuntu-save"), fmt.Errorf("failed to unlock ubuntu-save with fallback object") 4878 4879 default: 4880 c.Errorf("unexpected call to UnlockVolumeUsingSealedKeyIfEncrypted (num %d)", unlockVolumeWithSealedKeyCalls) 4881 return secboot.UnlockResult{}, fmt.Errorf("broken test") 4882 } 4883 }) 4884 defer restore() 4885 4886 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsHostWritableDir, "foo", "") 4887 4888 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 4889 // nothing can call this function in the tested scenario 4890 c.Fatalf("unexpected call") 4891 return secboot.UnlockResult{}, fmt.Errorf("unexpected call") 4892 }) 4893 defer restore() 4894 4895 measureEpochCalls := 0 4896 measureModelCalls := 0 4897 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 4898 measureEpochCalls++ 4899 return nil 4900 }) 4901 defer restore() 4902 4903 var measuredModel *asserts.Model 4904 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 4905 measureModelCalls++ 4906 var err error 4907 measuredModel, err = findModel() 4908 if err != nil { 4909 return err 4910 } 4911 return nil 4912 }) 4913 defer restore() 4914 4915 restore = s.mockSystemdMountSequence(c, []systemdMount{ 4916 ubuntuLabelMount("ubuntu-seed", "recover"), 4917 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 4918 s.makeSeedSnapSystemdMount(snap.TypeKernel), 4919 s.makeSeedSnapSystemdMount(snap.TypeBase), 4920 { 4921 "tmpfs", 4922 boot.InitramfsDataDir, 4923 tmpfsMountOpts, 4924 }, 4925 { 4926 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 4927 boot.InitramfsUbuntuBootDir, 4928 needsFsckDiskMountOpts, 4929 }, 4930 }, nil) 4931 defer restore() 4932 4933 // ensure that we check that access to sealed keys were locked 4934 sealedKeysLocked := false 4935 restore = main.MockSecbootLockSealedKeys(func() error { 4936 sealedKeysLocked = true 4937 return nil 4938 }) 4939 defer restore() 4940 4941 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 4942 c.Assert(err, IsNil) 4943 4944 // we always need to lock access to sealed keys 4945 c.Check(sealedKeysLocked, Equals, true) 4946 4947 modeEnv := filepath.Join(boot.InitramfsRunMntDir, "data/system-data/var/lib/snapd/modeenv") 4948 c.Check(modeEnv, testutil.FileEquals, `mode=recover 4949 recovery_system=20191118 4950 base=core20_1.snap 4951 model=my-brand/my-model 4952 grade=signed 4953 `) 4954 4955 checkDegradedJSON(c, map[string]interface{}{ 4956 "ubuntu-boot": map[string]interface{}{ 4957 "device": "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 4958 "mount-state": "mounted", 4959 "find-state": "found", 4960 "mount-location": boot.InitramfsUbuntuBootDir, 4961 }, 4962 "ubuntu-data": map[string]interface{}{ 4963 "find-state": "found", 4964 "device": "/dev/disk/by-partuuid/ubuntu-data-enc-partuuid", 4965 "unlock-state": "error-unlocking", 4966 }, 4967 "ubuntu-save": map[string]interface{}{ 4968 "find-state": "found", 4969 "device": "/dev/disk/by-partuuid/ubuntu-save-enc-partuuid", 4970 "unlock-state": "error-unlocking", 4971 }, 4972 "error-log": []interface{}{ 4973 "cannot unlock encrypted ubuntu-data (device /dev/disk/by-partuuid/ubuntu-data-enc-partuuid) with sealed run key: failed to unlock ubuntu-data with run object", 4974 "cannot unlock encrypted ubuntu-data partition with sealed fallback key: failed to unlock ubuntu-data with fallback object", 4975 "cannot unlock encrypted ubuntu-save partition with sealed fallback key: failed to unlock ubuntu-save with fallback object", 4976 }, 4977 }) 4978 4979 bloader2, err := bootloader.Find("", nil) 4980 c.Assert(err, IsNil) 4981 m, err := bloader2.GetBootVars("snapd_recovery_system", "snapd_recovery_mode") 4982 c.Assert(err, IsNil) 4983 c.Assert(m, DeepEquals, map[string]string{ 4984 "snapd_recovery_system": "20191118", 4985 "snapd_recovery_mode": "run", 4986 }) 4987 4988 // since we didn't mount data at all, we won't have copied in files from 4989 // there and instead will copy safe defaults to the ephemeral data 4990 c.Assert(filepath.Join(boot.InitramfsRunMntDir, "/data/system-data/var/lib/console-conf/complete"), testutil.FilePresent) 4991 4992 c.Check(dataActivationAttempts, Equals, 2) 4993 c.Check(saveUnsealActivationAttempted, Equals, true) 4994 c.Check(unlockVolumeWithSealedKeyCalls, Equals, 3) 4995 c.Check(measureEpochCalls, Equals, 1) 4996 c.Check(measureModelCalls, Equals, 1) 4997 c.Check(measuredModel, DeepEquals, s.model) 4998 4999 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 5000 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent) 5001 } 5002 5003 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedMismatchedMarker(c *C) { 5004 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 5005 5006 restore := main.MockPartitionUUIDForBootedKernelDisk("") 5007 defer restore() 5008 5009 // setup a bootloader for setting the bootenv after we are done 5010 bloader := bootloadertest.Mock("mock", c.MkDir()) 5011 bootloader.Force(bloader) 5012 defer bootloader.Force(nil) 5013 5014 restore = disks.MockMountPointDisksToPartitionMapping( 5015 map[disks.Mountpoint]*disks.MockDiskMapping{ 5016 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultEncBootDisk, 5017 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultEncBootDisk, 5018 { 5019 Mountpoint: boot.InitramfsHostUbuntuDataDir, 5020 IsDecryptedDevice: true, 5021 }: defaultEncBootDisk, 5022 { 5023 Mountpoint: boot.InitramfsUbuntuSaveDir, 5024 IsDecryptedDevice: true, 5025 }: defaultEncBootDisk, 5026 }, 5027 ) 5028 defer restore() 5029 5030 dataActivated := false 5031 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 5032 c.Assert(name, Equals, "ubuntu-data") 5033 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")) 5034 5035 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 5036 c.Assert(err, IsNil) 5037 c.Assert(encDevPartUUID, Equals, "ubuntu-data-enc-partuuid") 5038 c.Assert(opts.AllowRecoveryKey, Equals, false) 5039 c.Assert(opts.WhichModel, NotNil) 5040 dataActivated = true 5041 return happyUnlocked("ubuntu-data", secboot.UnlockedWithSealedKey), nil 5042 }) 5043 defer restore() 5044 5045 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsHostWritableDir, "foo", "other-marker") 5046 s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker") 5047 5048 saveActivated := false 5049 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 5050 c.Check(dataActivated, Equals, true, Commentf("ubuntu-data not activated yet")) 5051 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 5052 c.Assert(err, IsNil) 5053 c.Assert(encDevPartUUID, Equals, "ubuntu-save-enc-partuuid") 5054 c.Assert(key, DeepEquals, []byte("foo")) 5055 saveActivated = true 5056 return happyUnlocked("ubuntu-save", secboot.UnlockedWithKey), nil 5057 }) 5058 defer restore() 5059 5060 measureEpochCalls := 0 5061 measureModelCalls := 0 5062 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 5063 measureEpochCalls++ 5064 return nil 5065 }) 5066 defer restore() 5067 5068 var measuredModel *asserts.Model 5069 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 5070 measureModelCalls++ 5071 var err error 5072 measuredModel, err = findModel() 5073 if err != nil { 5074 return err 5075 } 5076 return nil 5077 }) 5078 defer restore() 5079 5080 restore = s.mockSystemdMountSequence(c, []systemdMount{ 5081 ubuntuLabelMount("ubuntu-seed", "recover"), 5082 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 5083 s.makeSeedSnapSystemdMount(snap.TypeKernel), 5084 s.makeSeedSnapSystemdMount(snap.TypeBase), 5085 { 5086 "tmpfs", 5087 boot.InitramfsDataDir, 5088 tmpfsMountOpts, 5089 }, 5090 { 5091 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 5092 boot.InitramfsUbuntuBootDir, 5093 needsFsckDiskMountOpts, 5094 }, 5095 { 5096 "/dev/mapper/ubuntu-data-random", 5097 boot.InitramfsHostUbuntuDataDir, 5098 needsNoSuidDiskMountOpts, 5099 }, 5100 { 5101 "/dev/mapper/ubuntu-save-random", 5102 boot.InitramfsUbuntuSaveDir, 5103 nil, 5104 }, 5105 }, nil) 5106 defer restore() 5107 5108 // ensure that we check that access to sealed keys were locked 5109 sealedKeysLocked := false 5110 restore = main.MockSecbootLockSealedKeys(func() error { 5111 sealedKeysLocked = true 5112 return nil 5113 }) 5114 defer restore() 5115 5116 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 5117 c.Assert(err, IsNil) 5118 5119 // we always need to lock access to sealed keys 5120 c.Check(sealedKeysLocked, Equals, true) 5121 5122 modeEnv := filepath.Join(boot.InitramfsWritableDir, "var/lib/snapd/modeenv") 5123 c.Check(modeEnv, testutil.FileEquals, `mode=recover 5124 recovery_system=20191118 5125 base=core20_1.snap 5126 model=my-brand/my-model 5127 grade=signed 5128 `) 5129 5130 checkDegradedJSON(c, map[string]interface{}{ 5131 "ubuntu-boot": map[string]interface{}{ 5132 "device": "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 5133 "mount-state": "mounted", 5134 "find-state": "found", 5135 "mount-location": boot.InitramfsUbuntuBootDir, 5136 }, 5137 "ubuntu-data": map[string]interface{}{ 5138 "device": "/dev/mapper/ubuntu-data-random", 5139 "unlock-state": "unlocked", 5140 "find-state": "found", 5141 "mount-state": "mounted-untrusted", 5142 "unlock-key": "run", 5143 "mount-location": boot.InitramfsHostUbuntuDataDir, 5144 }, 5145 "ubuntu-save": map[string]interface{}{ 5146 "device": "/dev/mapper/ubuntu-save-random", 5147 "unlock-key": "run", 5148 "unlock-state": "unlocked", 5149 "mount-state": "mounted", 5150 "find-state": "found", 5151 "mount-location": boot.InitramfsUbuntuSaveDir, 5152 }, 5153 "error-log": []interface{}{"cannot trust ubuntu-data, ubuntu-save and ubuntu-data are not marked as from the same install"}, 5154 }) 5155 5156 bloader2, err := bootloader.Find("", nil) 5157 c.Assert(err, IsNil) 5158 m, err := bloader2.GetBootVars("snapd_recovery_system", "snapd_recovery_mode") 5159 c.Assert(err, IsNil) 5160 c.Assert(m, DeepEquals, map[string]string{ 5161 "snapd_recovery_system": "20191118", 5162 "snapd_recovery_mode": "run", 5163 }) 5164 5165 // since we didn't mount data at all, we won't have copied in files from 5166 // there and instead will copy safe defaults to the ephemeral data 5167 c.Assert(filepath.Join(boot.InitramfsRunMntDir, "/data/system-data/var/lib/console-conf/complete"), testutil.FilePresent) 5168 5169 c.Check(dataActivated, Equals, true) 5170 c.Check(saveActivated, Equals, true) 5171 c.Check(measureEpochCalls, Equals, 1) 5172 c.Check(measureModelCalls, Equals, 1) 5173 c.Check(measuredModel, DeepEquals, s.model) 5174 5175 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 5176 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent) 5177 } 5178 5179 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedAttackerFSAttachedHappy(c *C) { 5180 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 5181 5182 restore := main.MockPartitionUUIDForBootedKernelDisk("") 5183 defer restore() 5184 5185 // setup a bootloader for setting the bootenv 5186 bloader := bootloadertest.Mock("mock", c.MkDir()) 5187 bootloader.Force(bloader) 5188 defer bootloader.Force(nil) 5189 5190 mockDisk := &disks.MockDiskMapping{ 5191 FilesystemLabelToPartUUID: map[string]string{ 5192 "ubuntu-seed": "ubuntu-seed-partuuid", 5193 "ubuntu-boot": "ubuntu-boot-partuuid", 5194 "ubuntu-data-enc": "ubuntu-data-enc-partuuid", 5195 "ubuntu-save-enc": "ubuntu-save-enc-partuuid", 5196 }, 5197 DiskHasPartitions: true, 5198 DevNum: "bootDev", 5199 } 5200 attackerDisk := &disks.MockDiskMapping{ 5201 FilesystemLabelToPartUUID: map[string]string{ 5202 "ubuntu-seed": "ubuntu-seed-attacker-partuuid", 5203 "ubuntu-boot": "ubuntu-boot-attacker-partuuid", 5204 "ubuntu-data-enc": "ubuntu-data-enc-attacker-partuuid", 5205 "ubuntu-save-enc": "ubuntu-save-enc-attacker-partuuid", 5206 }, 5207 DiskHasPartitions: true, 5208 DevNum: "attackerDev", 5209 } 5210 5211 restore = disks.MockMountPointDisksToPartitionMapping( 5212 map[disks.Mountpoint]*disks.MockDiskMapping{ 5213 {Mountpoint: boot.InitramfsUbuntuSeedDir}: mockDisk, 5214 {Mountpoint: boot.InitramfsUbuntuBootDir}: mockDisk, 5215 { 5216 Mountpoint: boot.InitramfsHostUbuntuDataDir, 5217 IsDecryptedDevice: true, 5218 }: mockDisk, 5219 { 5220 Mountpoint: boot.InitramfsUbuntuSaveDir, 5221 IsDecryptedDevice: true, 5222 }: mockDisk, 5223 // this is the attacker fs on a different disk 5224 {Mountpoint: "somewhere-else"}: attackerDisk, 5225 }, 5226 ) 5227 defer restore() 5228 5229 activated := false 5230 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 5231 c.Assert(name, Equals, "ubuntu-data") 5232 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 5233 c.Assert(err, IsNil) 5234 c.Assert(encDevPartUUID, Equals, "ubuntu-data-enc-partuuid") 5235 c.Assert(opts.AllowRecoveryKey, Equals, false) 5236 c.Assert(opts.WhichModel, NotNil) 5237 activated = true 5238 return happyUnlocked("ubuntu-data", secboot.UnlockedWithSealedKey), nil 5239 }) 5240 defer restore() 5241 5242 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsHostWritableDir, "foo", "marker") 5243 s.mockUbuntuSaveMarker(c, boot.InitramfsUbuntuSaveDir, "marker") 5244 5245 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 5246 encDevPartUUID, err := disk.FindMatchingPartitionUUIDWithFsLabel(name + "-enc") 5247 c.Assert(err, IsNil) 5248 c.Assert(encDevPartUUID, Equals, "ubuntu-save-enc-partuuid") 5249 c.Assert(key, DeepEquals, []byte("foo")) 5250 return happyUnlocked("ubuntu-save", secboot.UnlockedWithKey), nil 5251 }) 5252 defer restore() 5253 5254 measureEpochCalls := 0 5255 measureModelCalls := 0 5256 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 5257 measureEpochCalls++ 5258 return nil 5259 }) 5260 defer restore() 5261 5262 var measuredModel *asserts.Model 5263 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 5264 measureModelCalls++ 5265 var err error 5266 measuredModel, err = findModel() 5267 if err != nil { 5268 return err 5269 } 5270 return nil 5271 }) 5272 defer restore() 5273 5274 restore = s.mockSystemdMountSequence(c, []systemdMount{ 5275 ubuntuLabelMount("ubuntu-seed", "recover"), 5276 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 5277 s.makeSeedSnapSystemdMount(snap.TypeKernel), 5278 s.makeSeedSnapSystemdMount(snap.TypeBase), 5279 { 5280 "tmpfs", 5281 boot.InitramfsDataDir, 5282 tmpfsMountOpts, 5283 }, 5284 { 5285 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 5286 boot.InitramfsUbuntuBootDir, 5287 needsFsckDiskMountOpts, 5288 }, 5289 { 5290 "/dev/mapper/ubuntu-data-random", 5291 boot.InitramfsHostUbuntuDataDir, 5292 needsNoSuidDiskMountOpts, 5293 }, 5294 { 5295 "/dev/mapper/ubuntu-save-random", 5296 boot.InitramfsUbuntuSaveDir, 5297 nil, 5298 }, 5299 }, nil) 5300 defer restore() 5301 5302 s.testRecoverModeHappy(c) 5303 5304 c.Check(activated, Equals, true) 5305 c.Check(measureEpochCalls, Equals, 1) 5306 c.Check(measureModelCalls, Equals, 1) 5307 c.Check(measuredModel, DeepEquals, s.model) 5308 5309 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 5310 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent) 5311 } 5312 5313 func (s *initramfsMountsSuite) testInitramfsMountsInstallRecoverModeMeasure(c *C, mode string) { 5314 s.mockProcCmdlineContent(c, fmt.Sprintf("snapd_recovery_mode=%s snapd_recovery_system=%s", mode, s.sysLabel)) 5315 5316 modeMnts := []systemdMount{ 5317 ubuntuLabelMount("ubuntu-seed", mode), 5318 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 5319 s.makeSeedSnapSystemdMount(snap.TypeKernel), 5320 s.makeSeedSnapSystemdMount(snap.TypeBase), 5321 { 5322 "tmpfs", 5323 boot.InitramfsDataDir, 5324 tmpfsMountOpts, 5325 }, 5326 } 5327 5328 mockDiskMapping := map[disks.Mountpoint]*disks.MockDiskMapping{ 5329 {Mountpoint: boot.InitramfsUbuntuSeedDir}: { 5330 FilesystemLabelToPartUUID: map[string]string{ 5331 "ubuntu-seed": "ubuntu-seed-partuuid", 5332 }, 5333 DiskHasPartitions: true, 5334 }, 5335 } 5336 5337 if mode == "recover" { 5338 // setup a bootloader for setting the bootenv after we are done 5339 bloader := bootloadertest.Mock("mock", c.MkDir()) 5340 bootloader.Force(bloader) 5341 defer bootloader.Force(nil) 5342 5343 // add the expected mount of ubuntu-data onto the host data dir 5344 modeMnts = append(modeMnts, 5345 systemdMount{ 5346 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 5347 boot.InitramfsUbuntuBootDir, 5348 needsFsckDiskMountOpts, 5349 }, 5350 systemdMount{ 5351 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 5352 boot.InitramfsHostUbuntuDataDir, 5353 needsNoSuidDiskMountOpts, 5354 }, 5355 systemdMount{ 5356 "/dev/disk/by-partuuid/ubuntu-save-partuuid", 5357 boot.InitramfsUbuntuSaveDir, 5358 nil, 5359 }) 5360 5361 // also add the ubuntu-data and ubuntu-save fs labels to the 5362 // disk referenced by the ubuntu-seed partition 5363 disk := mockDiskMapping[disks.Mountpoint{Mountpoint: boot.InitramfsUbuntuSeedDir}] 5364 disk.FilesystemLabelToPartUUID["ubuntu-boot"] = "ubuntu-boot-partuuid" 5365 disk.FilesystemLabelToPartUUID["ubuntu-data"] = "ubuntu-data-partuuid" 5366 disk.FilesystemLabelToPartUUID["ubuntu-save"] = "ubuntu-save-partuuid" 5367 5368 // and also add the /run/mnt/host/ubuntu-{boot,data,save} mountpoints 5369 // for cross-checking after mounting 5370 mockDiskMapping[disks.Mountpoint{Mountpoint: boot.InitramfsUbuntuBootDir}] = disk 5371 mockDiskMapping[disks.Mountpoint{Mountpoint: boot.InitramfsHostUbuntuDataDir}] = disk 5372 mockDiskMapping[disks.Mountpoint{Mountpoint: boot.InitramfsUbuntuSaveDir}] = disk 5373 } 5374 5375 restore := disks.MockMountPointDisksToPartitionMapping(mockDiskMapping) 5376 defer restore() 5377 5378 measureEpochCalls := 0 5379 measureModelCalls := 0 5380 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 5381 measureEpochCalls++ 5382 return nil 5383 }) 5384 defer restore() 5385 5386 var measuredModel *asserts.Model 5387 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 5388 measureModelCalls++ 5389 var err error 5390 measuredModel, err = findModel() 5391 if err != nil { 5392 return err 5393 } 5394 return nil 5395 }) 5396 defer restore() 5397 5398 restore = s.mockSystemdMountSequence(c, modeMnts, nil) 5399 defer restore() 5400 5401 if mode == "recover" { 5402 // use the helper 5403 s.testRecoverModeHappy(c) 5404 } else { 5405 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 5406 c.Assert(err, IsNil) 5407 5408 modeEnv := filepath.Join(boot.InitramfsDataDir, "/system-data/var/lib/snapd/modeenv") 5409 c.Check(modeEnv, testutil.FileEquals, `mode=install 5410 recovery_system=20191118 5411 base=core20_1.snap 5412 model=my-brand/my-model 5413 grade=signed 5414 `) 5415 } 5416 5417 c.Check(measuredModel, NotNil) 5418 c.Check(measuredModel, DeepEquals, s.model) 5419 c.Check(measureEpochCalls, Equals, 1) 5420 c.Check(measureModelCalls, Equals, 1) 5421 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 5422 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, s.sysLabel+"-model-measured"), testutil.FilePresent) 5423 } 5424 5425 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeMeasure(c *C) { 5426 s.testInitramfsMountsInstallRecoverModeMeasure(c, "install") 5427 } 5428 5429 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeUnsetMeasure(c *C) { 5430 // TODO:UC20: eventually we should require snapd_recovery_mode to be set to 5431 // explicitly "install" for install mode, but we originally allowed 5432 // snapd_recovery_mode="" and interpreted it as install mode, so test that 5433 // case too 5434 s.testInitramfsMountsInstallRecoverModeMeasure(c, "") 5435 } 5436 5437 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeMeasure(c *C) { 5438 s.testInitramfsMountsInstallRecoverModeMeasure(c, "recover") 5439 } 5440 5441 func (s *initramfsMountsSuite) runInitramfsMountsUnencryptedTryRecovery(c *C, triedSystem bool) (err error) { 5442 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 5443 5444 restore := main.MockPartitionUUIDForBootedKernelDisk("") 5445 defer restore() 5446 restore = disks.MockMountPointDisksToPartitionMapping( 5447 map[disks.Mountpoint]*disks.MockDiskMapping{ 5448 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultBootWithSaveDisk, 5449 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootWithSaveDisk, 5450 {Mountpoint: boot.InitramfsHostUbuntuDataDir}: defaultBootWithSaveDisk, 5451 {Mountpoint: boot.InitramfsUbuntuSaveDir}: defaultBootWithSaveDisk, 5452 }, 5453 ) 5454 defer restore() 5455 restore = s.mockSystemdMountSequence(c, []systemdMount{ 5456 ubuntuLabelMount("ubuntu-seed", "recover"), 5457 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 5458 s.makeSeedSnapSystemdMount(snap.TypeKernel), 5459 s.makeSeedSnapSystemdMount(snap.TypeBase), 5460 { 5461 "tmpfs", 5462 boot.InitramfsDataDir, 5463 tmpfsMountOpts, 5464 }, 5465 { 5466 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 5467 boot.InitramfsUbuntuBootDir, 5468 needsFsckDiskMountOpts, 5469 }, 5470 { 5471 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 5472 boot.InitramfsHostUbuntuDataDir, 5473 needsNoSuidDiskMountOpts, 5474 }, 5475 { 5476 "/dev/disk/by-partuuid/ubuntu-save-partuuid", 5477 boot.InitramfsUbuntuSaveDir, 5478 nil, 5479 }, 5480 }, nil) 5481 defer restore() 5482 5483 if triedSystem { 5484 defer func() { 5485 err = recover().(error) 5486 }() 5487 } 5488 5489 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 5490 return err 5491 } 5492 5493 func (s *initramfsMountsSuite) testInitramfsMountsTryRecoveryHappy(c *C, happyStatus string) { 5494 rebootCalls := 0 5495 restore := boot.MockInitramfsReboot(func() error { 5496 rebootCalls++ 5497 return nil 5498 }) 5499 defer restore() 5500 5501 bl := bootloadertest.Mock("bootloader", c.MkDir()) 5502 bl.BootVars = map[string]string{ 5503 "recovery_system_status": happyStatus, 5504 "try_recovery_system": s.sysLabel, 5505 } 5506 bootloader.Force(bl) 5507 defer bootloader.Force(nil) 5508 5509 hostUbuntuData := filepath.Join(boot.InitramfsRunMntDir, "host/ubuntu-data/") 5510 mockedState := filepath.Join(hostUbuntuData, "system-data/var/lib/snapd/state.json") 5511 c.Assert(os.MkdirAll(filepath.Dir(mockedState), 0750), IsNil) 5512 c.Assert(ioutil.WriteFile(mockedState, []byte(mockStateContent), 0640), IsNil) 5513 5514 const triedSystem = true 5515 err := s.runInitramfsMountsUnencryptedTryRecovery(c, triedSystem) 5516 // due to hackery with replacing reboot, we expect a non nil error that 5517 // actually indicates a success 5518 c.Assert(err, ErrorMatches, `finalize try recovery system did not reboot, last error: <nil>`) 5519 5520 // modeenv is not written as reboot happens before that 5521 modeEnv := dirs.SnapModeenvFileUnder(boot.InitramfsWritableDir) 5522 c.Check(modeEnv, testutil.FileAbsent) 5523 c.Check(bl.BootVars, DeepEquals, map[string]string{ 5524 "recovery_system_status": "tried", 5525 "try_recovery_system": s.sysLabel, 5526 "snapd_recovery_mode": "run", 5527 "snapd_recovery_system": "", 5528 }) 5529 c.Check(rebootCalls, Equals, 1) 5530 } 5531 5532 func (s *initramfsMountsSuite) TestInitramfsMountsTryRecoveryHappyTry(c *C) { 5533 s.testInitramfsMountsTryRecoveryHappy(c, "try") 5534 } 5535 5536 func (s *initramfsMountsSuite) TestInitramfsMountsTryRecoveryHappyTried(c *C) { 5537 s.testInitramfsMountsTryRecoveryHappy(c, "tried") 5538 } 5539 5540 func (s *initramfsMountsSuite) testInitramfsMountsTryRecoveryInconsistent(c *C) { 5541 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 5542 5543 restore := main.MockPartitionUUIDForBootedKernelDisk("") 5544 defer restore() 5545 restore = disks.MockMountPointDisksToPartitionMapping( 5546 map[disks.Mountpoint]*disks.MockDiskMapping{ 5547 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultBootWithSaveDisk, 5548 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootWithSaveDisk, 5549 }, 5550 ) 5551 defer restore() 5552 restore = s.mockSystemdMountSequence(c, []systemdMount{ 5553 ubuntuLabelMount("ubuntu-seed", "recover"), 5554 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 5555 s.makeSeedSnapSystemdMount(snap.TypeKernel), 5556 s.makeSeedSnapSystemdMount(snap.TypeBase), 5557 { 5558 "tmpfs", 5559 boot.InitramfsDataDir, 5560 tmpfsMountOpts, 5561 }, 5562 }, nil) 5563 defer restore() 5564 5565 runParser := func() { 5566 main.Parser().ParseArgs([]string{"initramfs-mounts"}) 5567 } 5568 c.Assert(runParser, PanicMatches, `finalize try recovery system did not reboot, last error: <nil>`) 5569 } 5570 5571 func (s *initramfsMountsSuite) TestInitramfsMountsTryRecoveryInconsistentBogusStatus(c *C) { 5572 rebootCalls := 0 5573 restore := boot.MockInitramfsReboot(func() error { 5574 rebootCalls++ 5575 return nil 5576 }) 5577 defer restore() 5578 5579 bl := bootloadertest.Mock("bootloader", c.MkDir()) 5580 err := bl.SetBootVars(map[string]string{ 5581 "recovery_system_status": "bogus", 5582 "try_recovery_system": s.sysLabel, 5583 }) 5584 c.Assert(err, IsNil) 5585 bootloader.Force(bl) 5586 defer bootloader.Force(nil) 5587 5588 s.testInitramfsMountsTryRecoveryInconsistent(c) 5589 5590 vars, err := bl.GetBootVars("recovery_system_status", "try_recovery_system", 5591 "snapd_recovery_mode", "snapd_recovery_system") 5592 c.Assert(err, IsNil) 5593 c.Check(vars, DeepEquals, map[string]string{ 5594 "recovery_system_status": "", 5595 "try_recovery_system": s.sysLabel, 5596 "snapd_recovery_mode": "run", 5597 "snapd_recovery_system": "", 5598 }) 5599 c.Check(rebootCalls, Equals, 1) 5600 c.Check(s.logs.String(), testutil.Contains, `try recovery system state is inconsistent: unexpected recovery system status "bogus"`) 5601 } 5602 5603 func (s *initramfsMountsSuite) TestInitramfsMountsTryRecoveryInconsistentMissingLabel(c *C) { 5604 rebootCalls := 0 5605 restore := boot.MockInitramfsReboot(func() error { 5606 rebootCalls++ 5607 return nil 5608 }) 5609 defer restore() 5610 5611 bl := bootloadertest.Mock("bootloader", c.MkDir()) 5612 err := bl.SetBootVars(map[string]string{ 5613 "recovery_system_status": "try", 5614 "try_recovery_system": "", 5615 }) 5616 c.Assert(err, IsNil) 5617 bootloader.Force(bl) 5618 defer bootloader.Force(nil) 5619 5620 s.testInitramfsMountsTryRecoveryInconsistent(c) 5621 5622 vars, err := bl.GetBootVars("recovery_system_status", "try_recovery_system", 5623 "snapd_recovery_mode", "snapd_recovery_system") 5624 c.Assert(err, IsNil) 5625 c.Check(vars, DeepEquals, map[string]string{ 5626 "recovery_system_status": "", 5627 "try_recovery_system": "", 5628 "snapd_recovery_mode": "run", 5629 "snapd_recovery_system": "", 5630 }) 5631 c.Check(rebootCalls, Equals, 1) 5632 c.Check(s.logs.String(), testutil.Contains, `try recovery system state is inconsistent: try recovery system is unset but status is "try"`) 5633 } 5634 5635 func (s *initramfsMountsSuite) TestInitramfsMountsTryRecoveryDifferentSystem(c *C) { 5636 rebootCalls := 0 5637 restore := boot.MockInitramfsReboot(func() error { 5638 rebootCalls++ 5639 return nil 5640 }) 5641 defer restore() 5642 5643 bl := bootloadertest.Mock("bootloader", c.MkDir()) 5644 bl.BootVars = map[string]string{ 5645 "recovery_system_status": "try", 5646 // a different system is expected to be tried 5647 "try_recovery_system": "1234", 5648 } 5649 bootloader.Force(bl) 5650 defer bootloader.Force(nil) 5651 5652 hostUbuntuData := filepath.Join(boot.InitramfsRunMntDir, "host/ubuntu-data/") 5653 mockedState := filepath.Join(hostUbuntuData, "system-data/var/lib/snapd/state.json") 5654 c.Assert(os.MkdirAll(filepath.Dir(mockedState), 0750), IsNil) 5655 c.Assert(ioutil.WriteFile(mockedState, []byte(mockStateContent), 0640), IsNil) 5656 5657 const triedSystem = false 5658 err := s.runInitramfsMountsUnencryptedTryRecovery(c, triedSystem) 5659 c.Assert(err, IsNil) 5660 5661 // modeenv is written as we will seed the recovery system 5662 modeEnv := dirs.SnapModeenvFileUnder(boot.InitramfsWritableDir) 5663 c.Check(modeEnv, testutil.FileEquals, `mode=recover 5664 recovery_system=20191118 5665 base=core20_1.snap 5666 model=my-brand/my-model 5667 grade=signed 5668 `) 5669 c.Check(bl.BootVars, DeepEquals, map[string]string{ 5670 // variables not modified since they were set up for a different 5671 // system 5672 "recovery_system_status": "try", 5673 "try_recovery_system": "1234", 5674 // system is set up to go into run mode if rebooted 5675 "snapd_recovery_mode": "run", 5676 "snapd_recovery_system": s.sysLabel, 5677 }) 5678 // no reboot requests 5679 c.Check(rebootCalls, Equals, 0) 5680 } 5681 5682 func (s *initramfsMountsSuite) testInitramfsMountsTryRecoveryDegraded(c *C, expectedErr string, unlockDataFails, missingSaveKey bool) { 5683 // unlocking data and save failed, thus we consider this candidate 5684 // recovery system unusable 5685 5686 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 5687 5688 restore := main.MockPartitionUUIDForBootedKernelDisk("") 5689 defer restore() 5690 5691 bl := bootloadertest.Mock("bootloader", c.MkDir()) 5692 bl.BootVars = map[string]string{ 5693 "recovery_system_status": "try", 5694 "try_recovery_system": s.sysLabel, 5695 } 5696 bootloader.Force(bl) 5697 defer bootloader.Force(nil) 5698 5699 mountMappings := map[disks.Mountpoint]*disks.MockDiskMapping{ 5700 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultEncBootDisk, 5701 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultEncBootDisk, 5702 { 5703 Mountpoint: boot.InitramfsUbuntuSaveDir, 5704 IsDecryptedDevice: true, 5705 }: defaultEncBootDisk, 5706 } 5707 mountSequence := []systemdMount{ 5708 ubuntuLabelMount("ubuntu-seed", "recover"), 5709 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 5710 s.makeSeedSnapSystemdMount(snap.TypeKernel), 5711 s.makeSeedSnapSystemdMount(snap.TypeBase), 5712 { 5713 "tmpfs", 5714 boot.InitramfsDataDir, 5715 tmpfsMountOpts, 5716 }, 5717 { 5718 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 5719 boot.InitramfsUbuntuBootDir, 5720 needsFsckDiskMountOpts, 5721 }, 5722 } 5723 if !unlockDataFails { 5724 // unlocking data is successful in this scenario 5725 mountMappings[disks.Mountpoint{ 5726 Mountpoint: boot.InitramfsHostUbuntuDataDir, 5727 IsDecryptedDevice: true, 5728 }] = defaultEncBootDisk 5729 // and it got mounted too 5730 mountSequence = append(mountSequence, systemdMount{ 5731 "/dev/mapper/ubuntu-data-random", 5732 boot.InitramfsHostUbuntuDataDir, 5733 needsNoSuidDiskMountOpts, 5734 }) 5735 } 5736 if !missingSaveKey { 5737 s.mockUbuntuSaveKeyAndMarker(c, boot.InitramfsHostWritableDir, "foo", "marker") 5738 } 5739 5740 restore = disks.MockMountPointDisksToPartitionMapping(mountMappings) 5741 defer restore() 5742 unlockVolumeWithSealedKeyCalls := 0 5743 restore = main.MockSecbootUnlockVolumeUsingSealedKeyIfEncrypted(func(disk disks.Disk, name string, sealedEncryptionKeyFile string, opts *secboot.UnlockVolumeUsingSealedKeyOptions) (secboot.UnlockResult, error) { 5744 unlockVolumeWithSealedKeyCalls++ 5745 switch unlockVolumeWithSealedKeyCalls { 5746 5747 case 1: 5748 // ubuntu data can't be unlocked with run key 5749 c.Assert(name, Equals, "ubuntu-data") 5750 c.Assert(sealedEncryptionKeyFile, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")) 5751 if unlockDataFails { 5752 // ubuntu-data can't be unlocked with the run key 5753 return foundEncrypted("ubuntu-data"), fmt.Errorf("failed to unlock ubuntu-data with run object") 5754 } 5755 return happyUnlocked("ubuntu-data", secboot.UnlockedWithSealedKey), nil 5756 default: 5757 c.Errorf("unexpected call to UnlockVolumeUsingSealedKeyIfEncrypted (num %d)", unlockVolumeWithSealedKeyCalls) 5758 return secboot.UnlockResult{}, fmt.Errorf("broken test") 5759 } 5760 }) 5761 defer restore() 5762 unlockVolumeWithKeyCalls := 0 5763 restore = main.MockSecbootUnlockEncryptedVolumeUsingKey(func(disk disks.Disk, name string, key []byte) (secboot.UnlockResult, error) { 5764 unlockVolumeWithKeyCalls++ 5765 switch unlockVolumeWithKeyCalls { 5766 case 1: 5767 if unlockDataFails { 5768 // unlocking data failed, with fallback disabled we should never reach here 5769 return secboot.UnlockResult{}, fmt.Errorf("unexpected call to unlock ubuntu-save, broken test") 5770 } 5771 // no attempts to activate ubuntu-save yet 5772 c.Assert(name, Equals, "ubuntu-save") 5773 c.Assert(key, DeepEquals, []byte("foo")) 5774 return foundEncrypted("ubuntu-save"), fmt.Errorf("failed to unlock ubuntu-save with key object") 5775 default: 5776 c.Fatalf("unexpected call") 5777 return secboot.UnlockResult{}, fmt.Errorf("unexpected call") 5778 } 5779 }) 5780 defer restore() 5781 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { return nil }) 5782 defer restore() 5783 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 5784 return nil 5785 }) 5786 defer restore() 5787 5788 restore = s.mockSystemdMountSequence(c, mountSequence, nil) 5789 defer restore() 5790 5791 restore = main.MockSecbootLockSealedKeys(func() error { 5792 return nil 5793 }) 5794 defer restore() 5795 5796 c.Assert(func() { main.Parser().ParseArgs([]string{"initramfs-mounts"}) }, PanicMatches, 5797 expectedErr) 5798 5799 modeEnv := filepath.Join(boot.InitramfsRunMntDir, "data/system-data/var/lib/snapd/modeenv") 5800 // modeenv is not written when trying out a recovery system 5801 c.Check(modeEnv, testutil.FileAbsent) 5802 5803 // degraded file is not written out as we always reboot 5804 c.Check(filepath.Join(dirs.SnapBootstrapRunDir, "degraded.json"), testutil.FileAbsent) 5805 5806 c.Check(bl.BootVars, DeepEquals, map[string]string{ 5807 // variables not modified since the system is unsuccessful 5808 "recovery_system_status": "try", 5809 "try_recovery_system": s.sysLabel, 5810 // system is set up to go into run more if rebooted 5811 "snapd_recovery_mode": "run", 5812 // recovery system is cleared 5813 "snapd_recovery_system": "", 5814 }) 5815 } 5816 5817 func (s *initramfsMountsSuite) TestInitramfsMountsTryRecoveryDegradedStopAfterData(c *C) { 5818 rebootCalls := 0 5819 restore := boot.MockInitramfsReboot(func() error { 5820 rebootCalls++ 5821 return nil 5822 }) 5823 defer restore() 5824 5825 expectedErr := `finalize try recovery system did not reboot, last error: <nil>` 5826 const unlockDataFails = true 5827 const missingSaveKey = true 5828 s.testInitramfsMountsTryRecoveryDegraded(c, expectedErr, unlockDataFails, missingSaveKey) 5829 5830 // reboot was requested 5831 c.Check(rebootCalls, Equals, 1) 5832 c.Check(s.logs.String(), testutil.Contains, fmt.Sprintf(`try recovery system %q failed: cannot unlock ubuntu-data (fallback disabled)`, s.sysLabel)) 5833 } 5834 5835 func (s *initramfsMountsSuite) TestInitramfsMountsTryRecoveryDegradedStopAfterSaveUnlockFailed(c *C) { 5836 rebootCalls := 0 5837 restore := boot.MockInitramfsReboot(func() error { 5838 rebootCalls++ 5839 return nil 5840 }) 5841 defer restore() 5842 5843 expectedErr := `finalize try recovery system did not reboot, last error: <nil>` 5844 const unlockDataFails = false 5845 const missingSaveKey = false 5846 s.testInitramfsMountsTryRecoveryDegraded(c, expectedErr, unlockDataFails, missingSaveKey) 5847 5848 // reboot was requested 5849 c.Check(rebootCalls, Equals, 1) 5850 c.Check(s.logs.String(), testutil.Contains, fmt.Sprintf(`try recovery system %q failed: cannot unlock ubuntu-save (fallback disabled)`, s.sysLabel)) 5851 } 5852 5853 func (s *initramfsMountsSuite) TestInitramfsMountsTryRecoveryDegradedStopAfterSaveMissingKey(c *C) { 5854 rebootCalls := 0 5855 restore := boot.MockInitramfsReboot(func() error { 5856 rebootCalls++ 5857 return nil 5858 }) 5859 defer restore() 5860 5861 expectedErr := `finalize try recovery system did not reboot, last error: <nil>` 5862 const unlockDataFails = false 5863 const missingSaveKey = true 5864 s.testInitramfsMountsTryRecoveryDegraded(c, expectedErr, unlockDataFails, missingSaveKey) 5865 5866 // reboot was requested 5867 c.Check(rebootCalls, Equals, 1) 5868 c.Check(s.logs.String(), testutil.Contains, fmt.Sprintf(`try recovery system %q failed: cannot unlock ubuntu-save (fallback disabled)`, s.sysLabel)) 5869 } 5870 5871 func (s *initramfsMountsSuite) TestInitramfsMountsTryRecoveryDegradedRebootFails(c *C) { 5872 rebootCalls := 0 5873 restore := boot.MockInitramfsReboot(func() error { 5874 rebootCalls++ 5875 return fmt.Errorf("reboot fails") 5876 }) 5877 defer restore() 5878 5879 expectedErr := `finalize try recovery system did not reboot, last error: cannot reboot to run system: reboot fails` 5880 const unlockDataFails = false 5881 const unlockSaveFails = false 5882 s.testInitramfsMountsTryRecoveryDegraded(c, expectedErr, unlockDataFails, unlockSaveFails) 5883 5884 // reboot was requested 5885 c.Check(rebootCalls, Equals, 1) 5886 } 5887 5888 func (s *initramfsMountsSuite) TestInitramfsMountsTryRecoveryHealthCheckFails(c *C) { 5889 rebootCalls := 0 5890 restore := boot.MockInitramfsReboot(func() error { 5891 rebootCalls++ 5892 return nil 5893 }) 5894 defer restore() 5895 5896 bl := bootloadertest.Mock("bootloader", c.MkDir()) 5897 bl.BootVars = map[string]string{ 5898 "recovery_system_status": "try", 5899 "try_recovery_system": s.sysLabel, 5900 } 5901 bootloader.Force(bl) 5902 defer bootloader.Force(nil) 5903 5904 // prepare some state for the recovery process to reach a point where 5905 // the health check can be executed 5906 hostUbuntuData := filepath.Join(boot.InitramfsRunMntDir, "host/ubuntu-data/") 5907 mockedState := filepath.Join(hostUbuntuData, "system-data/var/lib/snapd/state.json") 5908 c.Assert(os.MkdirAll(filepath.Dir(mockedState), 0750), IsNil) 5909 c.Assert(ioutil.WriteFile(mockedState, []byte(mockStateContent), 0640), IsNil) 5910 5911 restore = main.MockTryRecoverySystemHealthCheck(func() error { 5912 return fmt.Errorf("mock failure") 5913 }) 5914 defer restore() 5915 5916 const triedSystem = true 5917 err := s.runInitramfsMountsUnencryptedTryRecovery(c, triedSystem) 5918 c.Assert(err, ErrorMatches, `finalize try recovery system did not reboot, last error: <nil>`) 5919 5920 modeEnv := filepath.Join(boot.InitramfsRunMntDir, "data/system-data/var/lib/snapd/modeenv") 5921 // modeenv is not written when trying out a recovery system 5922 c.Check(modeEnv, testutil.FileAbsent) 5923 c.Check(bl.BootVars, DeepEquals, map[string]string{ 5924 // variables not modified since the health check failed 5925 "recovery_system_status": "try", 5926 "try_recovery_system": s.sysLabel, 5927 // but system is set up to go back to run mode 5928 "snapd_recovery_mode": "run", 5929 "snapd_recovery_system": "", 5930 }) 5931 // reboot was requested 5932 c.Check(rebootCalls, Equals, 1) 5933 c.Check(s.logs.String(), testutil.Contains, `try recovery system health check failed: mock failure`) 5934 }