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