github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "strings" 29 "time" 30 31 . "gopkg.in/check.v1" 32 33 "github.com/snapcore/snapd/asserts" 34 "github.com/snapcore/snapd/asserts/assertstest" 35 "github.com/snapcore/snapd/boot" 36 "github.com/snapcore/snapd/boot/boottest" 37 "github.com/snapcore/snapd/bootloader" 38 "github.com/snapcore/snapd/bootloader/bootloadertest" 39 main "github.com/snapcore/snapd/cmd/snap-bootstrap" 40 "github.com/snapcore/snapd/dirs" 41 "github.com/snapcore/snapd/logger" 42 "github.com/snapcore/snapd/osutil/disks" 43 "github.com/snapcore/snapd/seed" 44 "github.com/snapcore/snapd/seed/seedtest" 45 "github.com/snapcore/snapd/snap" 46 "github.com/snapcore/snapd/systemd" 47 "github.com/snapcore/snapd/testutil" 48 ) 49 50 var brandPrivKey, _ = assertstest.GenerateKey(752) 51 52 type initramfsMountsSuite struct { 53 testutil.BaseTest 54 55 // makes available a bunch of helper (like MakeAssertedSnap) 56 *seedtest.TestingSeed20 57 58 Stdout *bytes.Buffer 59 60 seedDir string 61 sysLabel string 62 model *asserts.Model 63 tmpDir string 64 65 kernel snap.PlaceInfo 66 kernelr2 snap.PlaceInfo 67 core20 snap.PlaceInfo 68 core20r2 snap.PlaceInfo 69 snapd snap.PlaceInfo 70 } 71 72 var _ = Suite(&initramfsMountsSuite{}) 73 74 var ( 75 tmpfsMountOpts = &main.SystemdMountOptions{ 76 Tmpfs: true, 77 } 78 needsFsckDiskMountOpts = &main.SystemdMountOptions{ 79 NeedsFsck: true, 80 } 81 82 defaultBootDisk = &disks.MockDiskMapping{ 83 FilesystemLabelToPartUUID: map[string]string{ 84 // ubuntu-boot not strictly necessary, since we mount it first we 85 // don't go looking for the label ubuntu-boot on a disk, we just 86 // mount it and hope it's what we need, unless we have UEFI vars or 87 // something 88 "ubuntu-boot": "ubuntu-boot-partuuid", 89 "ubuntu-seed": "ubuntu-seed-partuuid", 90 "ubuntu-data": "ubuntu-data-partuuid", 91 }, 92 DiskHasPartitions: true, 93 DevNum: "default", 94 } 95 96 defaultEncBootDisk = &disks.MockDiskMapping{ 97 FilesystemLabelToPartUUID: map[string]string{ 98 // ubuntu-boot not strictly necessary, since we mount it first we 99 // don't ever search a particular disk for the ubuntu-boot label, 100 // we just mount it and hope it's what we need, unless we have UEFI 101 // vars or something a la boot.PartitionUUIDForBootedKernelDisk 102 "ubuntu-boot": "ubuntu-boot-partuuid", 103 "ubuntu-seed": "ubuntu-seed-partuuid", 104 "ubuntu-data-enc": "ubuntu-data-enc-partuuid", 105 }, 106 DiskHasPartitions: true, 107 DevNum: "defaultEncDev", 108 } 109 110 mockStateContent = `{"data":{"auth":{"users":[{"id":1,"name":"mvo"}],"macaroon-key":"not-a-cookie","last-id":1}},"some":{"other":"stuff"}}` 111 ) 112 113 // because 1.9 vet does not like xerrors.Errorf(".. %w") 114 type mockedWrappedError struct { 115 err error 116 fmt string 117 } 118 119 func (m *mockedWrappedError) Unwrap() error { return m.err } 120 121 func (m *mockedWrappedError) Error() string { return fmt.Sprintf(m.fmt, m.err) } 122 123 func (s *initramfsMountsSuite) SetUpTest(c *C) { 124 s.BaseTest.SetUpTest(c) 125 126 s.Stdout = bytes.NewBuffer(nil) 127 128 _, restore := logger.MockLogger() 129 s.AddCleanup(restore) 130 131 s.tmpDir = c.MkDir() 132 133 // mock /run/mnt 134 dirs.SetRootDir(s.tmpDir) 135 restore = func() { dirs.SetRootDir("") } 136 s.AddCleanup(restore) 137 138 // pretend /run/mnt/ubuntu-seed has a valid seed 139 s.seedDir = boot.InitramfsUbuntuSeedDir 140 141 // now create a minimal uc20 seed dir with snaps/assertions 142 seed20 := &seedtest.TestingSeed20{SeedDir: s.seedDir} 143 seed20.SetupAssertSigning("canonical") 144 restore = seed.MockTrusted(seed20.StoreSigning.Trusted) 145 s.AddCleanup(restore) 146 147 // XXX: we don't really use this but seedtest always expects my-brand 148 seed20.Brands.Register("my-brand", brandPrivKey, map[string]interface{}{ 149 "verification": "verified", 150 }) 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", nil, 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 s.sysLabel = "20191118" 159 s.model = seed20.MakeSeed(c, s.sysLabel, "my-brand", "my-model", map[string]interface{}{ 160 "display-name": "my model", 161 "architecture": "amd64", 162 "base": "core20", 163 "snaps": []interface{}{ 164 map[string]interface{}{ 165 "name": "pc-kernel", 166 "id": seed20.AssertedSnapID("pc-kernel"), 167 "type": "kernel", 168 "default-channel": "20", 169 }, 170 map[string]interface{}{ 171 "name": "pc", 172 "id": seed20.AssertedSnapID("pc"), 173 "type": "gadget", 174 "default-channel": "20", 175 }}, 176 }, nil) 177 178 // make test snap PlaceInfo's for various boot functionality 179 var err error 180 s.kernel, err = snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap") 181 c.Assert(err, IsNil) 182 183 s.core20, err = snap.ParsePlaceInfoFromSnapFileName("core20_1.snap") 184 c.Assert(err, IsNil) 185 186 s.kernelr2, err = snap.ParsePlaceInfoFromSnapFileName("pc-kernel_2.snap") 187 c.Assert(err, IsNil) 188 189 s.core20r2, err = snap.ParsePlaceInfoFromSnapFileName("core20_2.snap") 190 c.Assert(err, IsNil) 191 192 s.snapd, err = snap.ParsePlaceInfoFromSnapFileName("snapd_1.snap") 193 c.Assert(err, IsNil) 194 195 // by default mock that we don't have UEFI vars, etc. to get the booted 196 // kernel partition partition uuid 197 s.AddCleanup(main.MockPartitionUUIDForBootedKernelDisk("")) 198 } 199 200 // makeSnapFilesOnEarlyBootUbuntuData creates the snap files on ubuntu-data as 201 // we 202 func makeSnapFilesOnEarlyBootUbuntuData(c *C, snaps ...snap.PlaceInfo) { 203 snapDir := dirs.SnapBlobDirUnder(boot.InitramfsWritableDir) 204 err := os.MkdirAll(snapDir, 0755) 205 c.Assert(err, IsNil) 206 for _, sn := range snaps { 207 snFilename := sn.Filename() 208 err = ioutil.WriteFile(filepath.Join(snapDir, snFilename), nil, 0644) 209 c.Assert(err, IsNil) 210 } 211 } 212 213 func (s *initramfsMountsSuite) mockProcCmdlineContent(c *C, newContent string) { 214 mockProcCmdline := filepath.Join(c.MkDir(), "proc-cmdline") 215 err := ioutil.WriteFile(mockProcCmdline, []byte(newContent), 0644) 216 c.Assert(err, IsNil) 217 restore := boot.MockProcCmdline(mockProcCmdline) 218 s.AddCleanup(restore) 219 } 220 221 func (s *initramfsMountsSuite) TestInitramfsMountsNoModeError(c *C) { 222 s.mockProcCmdlineContent(c, "nothing-to-see") 223 224 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 225 c.Assert(err, ErrorMatches, "cannot detect mode nor recovery system to use") 226 } 227 228 func (s *initramfsMountsSuite) TestInitramfsMountsUnknownMode(c *C) { 229 s.mockProcCmdlineContent(c, "snapd_recovery_mode=install-foo") 230 231 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 232 c.Assert(err, ErrorMatches, `cannot use unknown mode "install-foo"`) 233 } 234 235 type systemdMount struct { 236 what string 237 where string 238 opts *main.SystemdMountOptions 239 } 240 241 // this is a function so we evaluate InitramfsUbuntuBootDir, etc at the time of 242 // the test to pick up test-specific dirs.GlobalRootDir 243 func ubuntuLabelMount(label string, mode string) systemdMount { 244 mnt := systemdMount{ 245 opts: needsFsckDiskMountOpts, 246 } 247 switch label { 248 case "ubuntu-boot": 249 mnt.what = "/dev/disk/by-label/ubuntu-boot" 250 mnt.where = boot.InitramfsUbuntuBootDir 251 case "ubuntu-seed": 252 mnt.what = "/dev/disk/by-label/ubuntu-seed" 253 mnt.where = boot.InitramfsUbuntuSeedDir 254 // don't fsck in run mode 255 if mode == "run" { 256 mnt.opts = nil 257 } 258 case "ubuntu-data": 259 mnt.what = "/dev/disk/by-label/ubuntu-data" 260 mnt.where = boot.InitramfsDataDir 261 } 262 263 return mnt 264 } 265 266 // ubuntuPartUUIDMount returns a systemdMount for the partuuid disk, expecting 267 // that the partuuid contains in it the expected label for easier coding 268 func ubuntuPartUUIDMount(partuuid string, mode string) systemdMount { 269 mnt := systemdMount{ 270 opts: needsFsckDiskMountOpts, 271 } 272 mnt.what = filepath.Join("/dev/disk/by-partuuid", partuuid) 273 switch { 274 case strings.Contains(partuuid, "ubuntu-boot"): 275 mnt.where = boot.InitramfsUbuntuBootDir 276 case strings.Contains(partuuid, "ubuntu-seed"): 277 mnt.where = boot.InitramfsUbuntuSeedDir 278 // don't fsck in run mode 279 if mode == "run" { 280 mnt.opts = nil 281 } 282 case strings.Contains(partuuid, "ubuntu-data"): 283 mnt.where = boot.InitramfsDataDir 284 } 285 286 return mnt 287 } 288 289 func (s *initramfsMountsSuite) makeSeedSnapSystemdMount(typ snap.Type) systemdMount { 290 mnt := systemdMount{} 291 var name, dir string 292 switch typ { 293 case snap.TypeSnapd: 294 name = "snapd" 295 dir = "snapd" 296 case snap.TypeBase: 297 name = "core20" 298 dir = "base" 299 case snap.TypeKernel: 300 name = "pc-kernel" 301 dir = "kernel" 302 } 303 mnt.what = filepath.Join(s.seedDir, "snaps", name+"_1.snap") 304 mnt.where = filepath.Join(boot.InitramfsRunMntDir, dir) 305 306 return mnt 307 } 308 309 func (s *initramfsMountsSuite) makeRunSnapSystemdMount(typ snap.Type, sn snap.PlaceInfo) systemdMount { 310 mnt := systemdMount{} 311 var dir string 312 switch typ { 313 case snap.TypeSnapd: 314 dir = "snapd" 315 case snap.TypeBase: 316 dir = "base" 317 case snap.TypeKernel: 318 dir = "kernel" 319 } 320 321 mnt.what = filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), sn.Filename()) 322 mnt.where = filepath.Join(boot.InitramfsRunMntDir, dir) 323 324 return mnt 325 } 326 327 func (s *initramfsMountsSuite) mockSystemdMountSequence(c *C, mounts []systemdMount, comment CommentInterface) (restore func()) { 328 n := 0 329 if comment == nil { 330 comment = Commentf("") 331 } 332 s.AddCleanup(func() { 333 // make sure that after the test is done, we had as many mount calls as 334 // mocked mounts 335 c.Assert(n, Equals, len(mounts), comment) 336 }) 337 return main.MockSystemdMount(func(what, where string, opts *main.SystemdMountOptions) error { 338 n++ 339 c.Assert(n <= len(mounts), Equals, true) 340 if n > len(mounts) { 341 return fmt.Errorf("unexpected systemd-mount call: %s, %s, %+v", what, where, opts) 342 } 343 mnt := mounts[n-1] 344 c.Assert(what, Equals, mnt.what, comment) 345 c.Assert(where, Equals, mnt.where, comment) 346 c.Assert(opts, DeepEquals, mnt.opts, comment) 347 return nil 348 }) 349 } 350 351 // TODO:UC20: write version that actually calls a mocked systemd-mount and 352 // ensures the right symlinks are there, etc. 353 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeHappy(c *C) { 354 s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel) 355 356 restore := s.mockSystemdMountSequence(c, []systemdMount{ 357 ubuntuLabelMount("ubuntu-seed", "install"), 358 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 359 s.makeSeedSnapSystemdMount(snap.TypeKernel), 360 s.makeSeedSnapSystemdMount(snap.TypeBase), 361 { 362 "tmpfs", 363 boot.InitramfsDataDir, 364 tmpfsMountOpts, 365 }, 366 }, nil) 367 defer restore() 368 369 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 370 c.Assert(err, IsNil) 371 372 modeEnv := dirs.SnapModeenvFileUnder(boot.InitramfsWritableDir) 373 c.Check(modeEnv, testutil.FileEquals, `mode=install 374 recovery_system=20191118 375 `) 376 cloudInitDisable := filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled") 377 c.Check(cloudInitDisable, testutil.FilePresent) 378 } 379 380 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeBootedKernelPartitionUUIDHappy(c *C) { 381 s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel) 382 383 restore := main.MockPartitionUUIDForBootedKernelDisk("specific-ubuntu-seed-partuuid") 384 defer restore() 385 386 restore = s.mockSystemdMountSequence(c, []systemdMount{ 387 { 388 "/dev/disk/by-partuuid/specific-ubuntu-seed-partuuid", 389 boot.InitramfsUbuntuSeedDir, 390 needsFsckDiskMountOpts, 391 }, 392 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 393 s.makeSeedSnapSystemdMount(snap.TypeKernel), 394 s.makeSeedSnapSystemdMount(snap.TypeBase), 395 { 396 "tmpfs", 397 boot.InitramfsDataDir, 398 tmpfsMountOpts, 399 }, 400 }, nil) 401 defer restore() 402 403 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 404 c.Assert(err, IsNil) 405 406 modeEnv := dirs.SnapModeenvFileUnder(boot.InitramfsWritableDir) 407 c.Check(modeEnv, testutil.FileEquals, `mode=install 408 recovery_system=20191118 409 `) 410 cloudInitDisable := filepath.Join(boot.InitramfsWritableDir, "_writable_defaults/etc/cloud/cloud-init.disabled") 411 c.Check(cloudInitDisable, testutil.FilePresent) 412 } 413 414 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeHappy(c *C) { 415 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 416 417 restore := disks.MockMountPointDisksToPartitionMapping( 418 map[disks.Mountpoint]*disks.MockDiskMapping{ 419 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootDisk, 420 {Mountpoint: boot.InitramfsDataDir}: defaultBootDisk, 421 }, 422 ) 423 defer restore() 424 425 restore = s.mockSystemdMountSequence(c, []systemdMount{ 426 ubuntuLabelMount("ubuntu-boot", "run"), 427 ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"), 428 ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"), 429 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 430 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 431 }, nil) 432 defer restore() 433 434 // mock a bootloader 435 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 436 bootloader.Force(bloader) 437 defer bootloader.Force(nil) 438 439 // set the current kernel 440 restore = bloader.SetEnabledKernel(s.kernel) 441 defer restore() 442 443 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 444 445 // write modeenv 446 modeEnv := boot.Modeenv{ 447 Mode: "run", 448 Base: s.core20.Filename(), 449 CurrentKernels: []string{s.kernel.Filename()}, 450 } 451 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 452 c.Assert(err, IsNil) 453 454 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 455 c.Assert(err, IsNil) 456 } 457 458 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeRealSystemdMountTimesOutNoMount(c *C) { 459 s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel) 460 461 testStart := time.Now() 462 timeCalls := 0 463 restore := main.MockTimeNow(func() time.Time { 464 timeCalls++ 465 switch timeCalls { 466 case 1, 2: 467 return testStart 468 case 3: 469 // 1:31 later, we should time out 470 return testStart.Add(1*time.Minute + 31*time.Second) 471 default: 472 c.Errorf("unexpected time.Now() call (%d)", timeCalls) 473 // we want the test to fail at some point and not run forever, so 474 // move time way forward to make it for sure time out 475 return testStart.Add(10000 * time.Hour) 476 } 477 }) 478 defer restore() 479 480 cmd := testutil.MockCommand(c, "systemd-mount", ``) 481 defer cmd.Restore() 482 483 isMountedCalls := 0 484 restore = main.MockOsutilIsMounted(func(where string) (bool, error) { 485 isMountedCalls++ 486 switch isMountedCalls { 487 // always return false for the mount 488 case 1, 2: 489 c.Assert(where, Equals, boot.InitramfsUbuntuSeedDir) 490 return false, nil 491 default: 492 // shouldn't be called more than twice due to the time.Now() mocking 493 c.Errorf("test broken, IsMounted called too many (%d) times", isMountedCalls) 494 return false, fmt.Errorf("test broken, IsMounted called too many (%d) times", isMountedCalls) 495 } 496 }) 497 defer restore() 498 499 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 500 c.Assert(err, ErrorMatches, fmt.Sprintf("timed out after 1m30s waiting for mount %s on %s", "/dev/disk/by-label/ubuntu-seed", boot.InitramfsUbuntuSeedDir)) 501 c.Check(s.Stdout.String(), Equals, "") 502 503 } 504 505 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeHappyRealSystemdMount(c *C) { 506 s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel) 507 508 baseMnt := filepath.Join(boot.InitramfsRunMntDir, "base") 509 kernelMnt := filepath.Join(boot.InitramfsRunMntDir, "kernel") 510 snapdMnt := filepath.Join(boot.InitramfsRunMntDir, "snapd") 511 512 // don't do anything from systemd-mount, we verify the arguments passed at 513 // the end with cmd.Calls 514 cmd := testutil.MockCommand(c, "systemd-mount", ``) 515 defer cmd.Restore() 516 517 // mock that in turn, /run/mnt/ubuntu-boot, /run/mnt/ubuntu-seed, etc. are 518 // mounted 519 n := 0 520 restore := main.MockOsutilIsMounted(func(where string) (bool, error) { 521 n++ 522 switch n { 523 // first call for each mount returns false, then returns true, this 524 // tests in the case where systemd is racy / inconsistent and things 525 // aren't mounted by the time systemd-mount returns 526 case 1, 2: 527 c.Assert(where, Equals, boot.InitramfsUbuntuSeedDir) 528 return n%2 == 0, nil 529 case 3, 4: 530 c.Assert(where, Equals, snapdMnt) 531 return n%2 == 0, nil 532 case 5, 6: 533 c.Assert(where, Equals, kernelMnt) 534 return n%2 == 0, nil 535 case 7, 8: 536 c.Assert(where, Equals, baseMnt) 537 return n%2 == 0, nil 538 case 9, 10: 539 c.Assert(where, Equals, boot.InitramfsDataDir) 540 return n%2 == 0, nil 541 default: 542 c.Errorf("unexpected IsMounted check on %s", where) 543 return false, fmt.Errorf("unexpected IsMounted check on %s", where) 544 } 545 }) 546 defer restore() 547 548 // mock a bootloader 549 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 550 bootloader.Force(bloader) 551 defer bootloader.Force(nil) 552 553 // set the current kernel 554 restore = bloader.SetEnabledKernel(s.kernel) 555 defer restore() 556 557 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 558 559 // write modeenv 560 modeEnv := boot.Modeenv{ 561 Mode: "run", 562 Base: s.core20.Filename(), 563 CurrentKernels: []string{s.kernel.Filename()}, 564 } 565 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 566 c.Assert(err, IsNil) 567 568 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 569 c.Assert(err, IsNil) 570 c.Check(s.Stdout.String(), Equals, "") 571 572 // check that all of the override files are present 573 for _, initrdUnit := range []string{ 574 "initrd.target", 575 "initrd-fs.target", 576 "initrd-switch-root.target", 577 "local-fs.target", 578 } { 579 for _, mountUnit := range []string{ 580 systemd.EscapeUnitNamePath(boot.InitramfsUbuntuSeedDir), 581 systemd.EscapeUnitNamePath(snapdMnt), 582 systemd.EscapeUnitNamePath(kernelMnt), 583 systemd.EscapeUnitNamePath(baseMnt), 584 systemd.EscapeUnitNamePath(boot.InitramfsDataDir), 585 } { 586 fname := fmt.Sprintf("snap_bootstrap_%s.conf", mountUnit) 587 unitFile := filepath.Join(dirs.GlobalRootDir, "/run/systemd/system", initrdUnit+".d", fname) 588 c.Assert(unitFile, testutil.FileEquals, fmt.Sprintf(`[Unit] 589 Requires=%[1]s 590 After=%[1]s 591 `, mountUnit+".mount")) 592 } 593 } 594 595 // 2 IsMounted calls per mount point, so 10 total IsMounted calls 596 c.Assert(n, Equals, 10) 597 598 c.Assert(cmd.Calls(), DeepEquals, [][]string{ 599 { 600 "systemd-mount", 601 "/dev/disk/by-label/ubuntu-seed", 602 boot.InitramfsUbuntuSeedDir, 603 "--no-pager", 604 "--no-ask-password", 605 "--fsck=yes", 606 }, 607 { 608 "systemd-mount", 609 filepath.Join(s.seedDir, "snaps", s.snapd.Filename()), 610 snapdMnt, 611 "--no-pager", 612 "--no-ask-password", 613 "--fsck=no", 614 }, 615 { 616 "systemd-mount", 617 filepath.Join(s.seedDir, "snaps", s.kernel.Filename()), 618 kernelMnt, 619 "--no-pager", 620 "--no-ask-password", 621 "--fsck=no", 622 }, 623 { 624 "systemd-mount", 625 filepath.Join(s.seedDir, "snaps", s.core20.Filename()), 626 baseMnt, 627 "--no-pager", 628 "--no-ask-password", 629 "--fsck=no", 630 }, 631 { 632 "systemd-mount", 633 "tmpfs", 634 boot.InitramfsDataDir, 635 "--no-pager", 636 "--no-ask-password", 637 "--type=tmpfs", 638 "--fsck=no", 639 }, 640 }) 641 } 642 643 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeHappyRealSystemdMount(c *C) { 644 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 645 646 baseMnt := filepath.Join(boot.InitramfsRunMntDir, "base") 647 kernelMnt := filepath.Join(boot.InitramfsRunMntDir, "kernel") 648 snapdMnt := filepath.Join(boot.InitramfsRunMntDir, "snapd") 649 650 restore := disks.MockMountPointDisksToPartitionMapping( 651 map[disks.Mountpoint]*disks.MockDiskMapping{ 652 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultBootDisk, 653 {Mountpoint: boot.InitramfsHostUbuntuDataDir}: defaultBootDisk, 654 }, 655 ) 656 defer restore() 657 658 // don't do anything from systemd-mount, we verify the arguments passed at 659 // the end with cmd.Calls 660 cmd := testutil.MockCommand(c, "systemd-mount", ``) 661 defer cmd.Restore() 662 663 // mock that in turn, /run/mnt/ubuntu-boot, /run/mnt/ubuntu-seed, etc. are 664 // mounted 665 n := 0 666 restore = main.MockOsutilIsMounted(func(where string) (bool, error) { 667 n++ 668 switch n { 669 // first call for each mount returns false, then returns true, this 670 // tests in the case where systemd is racy / inconsistent and things 671 // aren't mounted by the time systemd-mount returns 672 case 1, 2: 673 c.Assert(where, Equals, boot.InitramfsUbuntuSeedDir) 674 return n%2 == 0, nil 675 case 3, 4: 676 c.Assert(where, Equals, snapdMnt) 677 return n%2 == 0, nil 678 case 5, 6: 679 c.Assert(where, Equals, kernelMnt) 680 return n%2 == 0, nil 681 case 7, 8: 682 c.Assert(where, Equals, baseMnt) 683 return n%2 == 0, nil 684 case 9, 10: 685 c.Assert(where, Equals, boot.InitramfsDataDir) 686 return n%2 == 0, nil 687 case 11, 12: 688 c.Assert(where, Equals, boot.InitramfsHostUbuntuDataDir) 689 return n%2 == 0, nil 690 default: 691 c.Errorf("unexpected IsMounted check on %s", where) 692 return false, fmt.Errorf("unexpected IsMounted check on %s", where) 693 } 694 }) 695 defer restore() 696 697 // mock a bootloader 698 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 699 bootloader.Force(bloader) 700 defer bootloader.Force(nil) 701 702 // set the current kernel 703 restore = bloader.SetEnabledKernel(s.kernel) 704 defer restore() 705 706 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 707 708 // write modeenv 709 modeEnv := boot.Modeenv{ 710 Mode: "run", 711 Base: s.core20.Filename(), 712 CurrentKernels: []string{s.kernel.Filename()}, 713 } 714 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 715 c.Assert(err, IsNil) 716 717 s.testRecoverModeHappy(c) 718 719 c.Check(s.Stdout.String(), Equals, "") 720 721 // check that all of the override files are present 722 for _, initrdUnit := range []string{ 723 "initrd.target", 724 "initrd-fs.target", 725 "initrd-switch-root.target", 726 "local-fs.target", 727 } { 728 for _, mountUnit := range []string{ 729 systemd.EscapeUnitNamePath(boot.InitramfsUbuntuSeedDir), 730 systemd.EscapeUnitNamePath(snapdMnt), 731 systemd.EscapeUnitNamePath(kernelMnt), 732 systemd.EscapeUnitNamePath(baseMnt), 733 systemd.EscapeUnitNamePath(boot.InitramfsDataDir), 734 systemd.EscapeUnitNamePath(boot.InitramfsHostUbuntuDataDir), 735 } { 736 fname := fmt.Sprintf("snap_bootstrap_%s.conf", mountUnit) 737 unitFile := filepath.Join(dirs.GlobalRootDir, "/run/systemd/system", initrdUnit+".d", fname) 738 c.Assert(unitFile, testutil.FileEquals, fmt.Sprintf(`[Unit] 739 Requires=%[1]s 740 After=%[1]s 741 `, mountUnit+".mount")) 742 } 743 } 744 745 // 2 IsMounted calls per mount point, so 12 total IsMounted calls 746 c.Assert(n, Equals, 12) 747 748 c.Assert(cmd.Calls(), DeepEquals, [][]string{ 749 { 750 "systemd-mount", 751 "/dev/disk/by-label/ubuntu-seed", 752 boot.InitramfsUbuntuSeedDir, 753 "--no-pager", 754 "--no-ask-password", 755 "--fsck=yes", 756 }, 757 { 758 "systemd-mount", 759 filepath.Join(s.seedDir, "snaps", s.snapd.Filename()), 760 snapdMnt, 761 "--no-pager", 762 "--no-ask-password", 763 "--fsck=no", 764 }, 765 { 766 "systemd-mount", 767 filepath.Join(s.seedDir, "snaps", s.kernel.Filename()), 768 kernelMnt, 769 "--no-pager", 770 "--no-ask-password", 771 "--fsck=no", 772 }, 773 { 774 "systemd-mount", 775 filepath.Join(s.seedDir, "snaps", s.core20.Filename()), 776 baseMnt, 777 "--no-pager", 778 "--no-ask-password", 779 "--fsck=no", 780 }, 781 { 782 "systemd-mount", 783 "tmpfs", 784 boot.InitramfsDataDir, 785 "--no-pager", 786 "--no-ask-password", 787 "--type=tmpfs", 788 "--fsck=no", 789 }, 790 { 791 "systemd-mount", 792 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 793 boot.InitramfsHostUbuntuDataDir, 794 "--no-pager", 795 "--no-ask-password", 796 "--fsck=no", 797 }, 798 }) 799 } 800 801 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeHappyRealSystemdMount(c *C) { 802 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 803 804 restore := disks.MockMountPointDisksToPartitionMapping( 805 map[disks.Mountpoint]*disks.MockDiskMapping{ 806 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootDisk, 807 {Mountpoint: boot.InitramfsDataDir}: defaultBootDisk, 808 }, 809 ) 810 defer restore() 811 812 baseMnt := filepath.Join(boot.InitramfsRunMntDir, "base") 813 kernelMnt := filepath.Join(boot.InitramfsRunMntDir, "kernel") 814 815 // don't do anything from systemd-mount, we verify the arguments passed at 816 // the end with cmd.Calls 817 cmd := testutil.MockCommand(c, "systemd-mount", ``) 818 defer cmd.Restore() 819 820 // mock that in turn, /run/mnt/ubuntu-boot, /run/mnt/ubuntu-seed, etc. are 821 // mounted 822 n := 0 823 restore = main.MockOsutilIsMounted(func(where string) (bool, error) { 824 n++ 825 switch n { 826 // first call for each mount returns false, then returns true, this 827 // tests in the case where systemd is racy / inconsistent and things 828 // aren't mounted by the time systemd-mount returns 829 case 1, 2: 830 c.Assert(where, Equals, boot.InitramfsUbuntuBootDir) 831 return n%2 == 0, nil 832 case 3, 4: 833 c.Assert(where, Equals, boot.InitramfsUbuntuSeedDir) 834 return n%2 == 0, nil 835 case 5, 6: 836 c.Assert(where, Equals, boot.InitramfsDataDir) 837 return n%2 == 0, nil 838 case 7, 8: 839 c.Assert(where, Equals, baseMnt) 840 return n%2 == 0, nil 841 case 9, 10: 842 c.Assert(where, Equals, kernelMnt) 843 return n%2 == 0, nil 844 default: 845 c.Errorf("unexpected IsMounted check on %s", where) 846 return false, fmt.Errorf("unexpected IsMounted check on %s", where) 847 } 848 }) 849 defer restore() 850 851 // mock a bootloader 852 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 853 bootloader.Force(bloader) 854 defer bootloader.Force(nil) 855 856 // set the current kernel 857 restore = bloader.SetEnabledKernel(s.kernel) 858 defer restore() 859 860 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 861 862 // write modeenv 863 modeEnv := boot.Modeenv{ 864 Mode: "run", 865 Base: s.core20.Filename(), 866 CurrentKernels: []string{s.kernel.Filename()}, 867 } 868 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 869 c.Assert(err, IsNil) 870 871 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 872 c.Assert(err, IsNil) 873 c.Check(s.Stdout.String(), Equals, "") 874 875 // check that all of the override files are present 876 for _, initrdUnit := range []string{ 877 "initrd.target", 878 "initrd-fs.target", 879 "initrd-switch-root.target", 880 "local-fs.target", 881 } { 882 for _, mountUnit := range []string{ 883 systemd.EscapeUnitNamePath(boot.InitramfsUbuntuBootDir), 884 systemd.EscapeUnitNamePath(boot.InitramfsUbuntuSeedDir), 885 systemd.EscapeUnitNamePath(boot.InitramfsDataDir), 886 systemd.EscapeUnitNamePath(baseMnt), 887 systemd.EscapeUnitNamePath(kernelMnt), 888 } { 889 fname := fmt.Sprintf("snap_bootstrap_%s.conf", mountUnit) 890 unitFile := filepath.Join(dirs.GlobalRootDir, "/run/systemd/system", initrdUnit+".d", fname) 891 c.Assert(unitFile, testutil.FileEquals, fmt.Sprintf(`[Unit] 892 Requires=%[1]s 893 After=%[1]s 894 `, mountUnit+".mount")) 895 } 896 } 897 898 // 2 IsMounted calls per mount point, so 10 total IsMounted calls 899 c.Assert(n, Equals, 10) 900 901 c.Assert(cmd.Calls(), DeepEquals, [][]string{ 902 { 903 "systemd-mount", 904 "/dev/disk/by-label/ubuntu-boot", 905 boot.InitramfsUbuntuBootDir, 906 "--no-pager", 907 "--no-ask-password", 908 "--fsck=yes", 909 }, 910 { 911 "systemd-mount", 912 "/dev/disk/by-partuuid/ubuntu-seed-partuuid", 913 boot.InitramfsUbuntuSeedDir, 914 "--no-pager", 915 "--no-ask-password", 916 "--fsck=no", 917 }, 918 { 919 "systemd-mount", 920 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 921 boot.InitramfsDataDir, 922 "--no-pager", 923 "--no-ask-password", 924 "--fsck=yes", 925 }, 926 { 927 "systemd-mount", 928 filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), s.core20.Filename()), 929 baseMnt, 930 "--no-pager", 931 "--no-ask-password", 932 "--fsck=no", 933 }, 934 { 935 "systemd-mount", 936 filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), s.kernel.Filename()), 937 kernelMnt, 938 "--no-pager", 939 "--no-ask-password", 940 "--fsck=no", 941 }, 942 }) 943 } 944 945 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeFirstBootRecoverySystemSetHappy(c *C) { 946 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 947 948 restore := disks.MockMountPointDisksToPartitionMapping( 949 map[disks.Mountpoint]*disks.MockDiskMapping{ 950 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootDisk, 951 {Mountpoint: boot.InitramfsDataDir}: defaultBootDisk, 952 }, 953 ) 954 defer restore() 955 956 restore = s.mockSystemdMountSequence(c, []systemdMount{ 957 ubuntuLabelMount("ubuntu-boot", "run"), 958 ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"), 959 ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"), 960 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 961 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 962 // RecoverySystem set makes us mount the snapd snap here 963 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 964 }, nil) 965 defer restore() 966 967 // mock a bootloader 968 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 969 bootloader.Force(bloader) 970 defer bootloader.Force(nil) 971 972 // set the current kernel 973 restore = bloader.SetEnabledKernel(s.kernel) 974 defer restore() 975 976 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 977 978 // write modeenv 979 modeEnv := boot.Modeenv{ 980 Mode: "run", 981 RecoverySystem: "20191118", 982 Base: s.core20.Filename(), 983 CurrentKernels: []string{s.kernel.Filename()}, 984 } 985 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 986 c.Assert(err, IsNil) 987 988 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 989 c.Assert(err, IsNil) 990 } 991 992 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeWithBootedKernelPartUUIDHappy(c *C) { 993 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 994 995 restore := main.MockPartitionUUIDForBootedKernelDisk("ubuntu-boot-partuuid") 996 defer restore() 997 998 restore = disks.MockMountPointDisksToPartitionMapping( 999 map[disks.Mountpoint]*disks.MockDiskMapping{ 1000 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootDisk, 1001 {Mountpoint: boot.InitramfsDataDir}: defaultBootDisk, 1002 }, 1003 ) 1004 defer restore() 1005 1006 restore = s.mockSystemdMountSequence(c, []systemdMount{ 1007 { 1008 "/dev/disk/by-partuuid/ubuntu-boot-partuuid", 1009 boot.InitramfsUbuntuBootDir, 1010 needsFsckDiskMountOpts, 1011 }, 1012 ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"), 1013 ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"), 1014 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 1015 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 1016 }, nil) 1017 defer restore() 1018 1019 // mock a bootloader 1020 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 1021 bootloader.Force(bloader) 1022 defer bootloader.Force(nil) 1023 1024 // set the current kernel 1025 restore = bloader.SetEnabledKernel(s.kernel) 1026 defer restore() 1027 1028 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 1029 1030 // write modeenv 1031 modeEnv := boot.Modeenv{ 1032 Mode: "run", 1033 Base: s.core20.Filename(), 1034 CurrentKernels: []string{s.kernel.Filename()}, 1035 } 1036 err := modeEnv.WriteTo(boot.InitramfsWritableDir) 1037 c.Assert(err, IsNil) 1038 1039 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 1040 c.Assert(err, IsNil) 1041 } 1042 1043 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeEncryptedDataHappy(c *C) { 1044 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 1045 1046 restore := disks.MockMountPointDisksToPartitionMapping( 1047 map[disks.Mountpoint]*disks.MockDiskMapping{ 1048 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultEncBootDisk, 1049 {Mountpoint: boot.InitramfsDataDir, IsDecryptedDevice: true}: defaultEncBootDisk, 1050 }, 1051 ) 1052 defer restore() 1053 1054 restore = s.mockSystemdMountSequence(c, []systemdMount{ 1055 ubuntuLabelMount("ubuntu-boot", "run"), 1056 ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"), 1057 { 1058 "path-to-device", 1059 boot.InitramfsDataDir, 1060 needsFsckDiskMountOpts, 1061 }, 1062 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 1063 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 1064 }, nil) 1065 defer restore() 1066 1067 // write the installed model like makebootable does it 1068 err := os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755) 1069 c.Assert(err, IsNil) 1070 mf, err := os.Create(filepath.Join(boot.InitramfsUbuntuBootDir, "model")) 1071 c.Assert(err, IsNil) 1072 defer mf.Close() 1073 err = asserts.NewEncoder(mf).Encode(s.model) 1074 c.Assert(err, IsNil) 1075 1076 activated := false 1077 restore = main.MockSecbootUnlockVolumeIfEncrypted(func(disk disks.Disk, name string, encryptionKeyDir string, lockKeysOnFinish bool) (string, bool, error) { 1078 c.Assert(name, Equals, "ubuntu-data") 1079 c.Assert(encryptionKeyDir, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-seed/device/fde")) 1080 c.Assert(lockKeysOnFinish, Equals, true) 1081 activated = true 1082 // return true because we are using an encrypted device 1083 return "path-to-device", true, nil 1084 }) 1085 defer restore() 1086 1087 measureEpochCalls := 0 1088 measureModelCalls := 0 1089 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 1090 measureEpochCalls++ 1091 return nil 1092 }) 1093 defer restore() 1094 1095 var measuredModel *asserts.Model 1096 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 1097 measureModelCalls++ 1098 var err error 1099 measuredModel, err = findModel() 1100 if err != nil { 1101 return err 1102 } 1103 return nil 1104 }) 1105 defer restore() 1106 1107 // mock a bootloader 1108 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 1109 bootloader.Force(bloader) 1110 defer bootloader.Force(nil) 1111 1112 // set the current kernel 1113 restore = bloader.SetEnabledKernel(s.kernel) 1114 defer restore() 1115 1116 makeSnapFilesOnEarlyBootUbuntuData(c, s.kernel, s.core20) 1117 1118 // write modeenv 1119 modeEnv := boot.Modeenv{ 1120 Mode: "run", 1121 Base: s.core20.Filename(), 1122 CurrentKernels: []string{s.kernel.Filename()}, 1123 } 1124 err = modeEnv.WriteTo(boot.InitramfsWritableDir) 1125 c.Assert(err, IsNil) 1126 1127 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 1128 c.Assert(err, IsNil) 1129 c.Check(activated, Equals, true) 1130 c.Check(measureEpochCalls, Equals, 1) 1131 c.Check(measureModelCalls, Equals, 1) 1132 c.Check(measuredModel, DeepEquals, s.model) 1133 1134 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 1135 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "run-model-measured"), testutil.FilePresent) 1136 } 1137 1138 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeEncryptedNoModel(c *C) { 1139 s.testInitramfsMountsEncryptedNoModel(c, "run", "", 1) 1140 } 1141 1142 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeEncryptedNoModel(c *C) { 1143 s.testInitramfsMountsEncryptedNoModel(c, "install", s.sysLabel, 0) 1144 } 1145 1146 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedNoModel(c *C) { 1147 s.testInitramfsMountsEncryptedNoModel(c, "recover", s.sysLabel, 0) 1148 } 1149 1150 func (s *initramfsMountsSuite) testInitramfsMountsEncryptedNoModel(c *C, mode, label string, expectedMeasureModelCalls int) { 1151 s.mockProcCmdlineContent(c, fmt.Sprintf("snapd_recovery_mode=%s", mode)) 1152 1153 // install and recover mounts are just ubuntu-seed before we fail 1154 var restore func() 1155 if mode == "run" { 1156 // run mode will mount ubuntu-boot and ubuntu-seed 1157 restore = s.mockSystemdMountSequence(c, []systemdMount{ 1158 ubuntuLabelMount("ubuntu-boot", mode), 1159 ubuntuPartUUIDMount("ubuntu-seed-partuuid", mode), 1160 }, nil) 1161 restore2 := disks.MockMountPointDisksToPartitionMapping( 1162 map[disks.Mountpoint]*disks.MockDiskMapping{ 1163 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultEncBootDisk, 1164 }, 1165 ) 1166 defer restore2() 1167 } else { 1168 restore = s.mockSystemdMountSequence(c, []systemdMount{ 1169 ubuntuLabelMount("ubuntu-seed", mode), 1170 }, nil) 1171 1172 // in install / recover mode the code doesn't make it far enough to do 1173 // any disk cross checking 1174 } 1175 defer restore() 1176 1177 if label != "" { 1178 s.mockProcCmdlineContent(c, 1179 fmt.Sprintf("snapd_recovery_mode=%s snapd_recovery_system=%s", mode, label)) 1180 // break the seed 1181 err := os.Remove(filepath.Join(s.seedDir, "systems", label, "model")) 1182 c.Assert(err, IsNil) 1183 } 1184 1185 measureEpochCalls := 0 1186 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 1187 measureEpochCalls++ 1188 return nil 1189 }) 1190 defer restore() 1191 1192 measureModelCalls := 0 1193 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 1194 measureModelCalls++ 1195 _, err := findModel() 1196 if err != nil { 1197 return err 1198 } 1199 return fmt.Errorf("unexpected call") 1200 }) 1201 defer restore() 1202 1203 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 1204 where := "/run/mnt/ubuntu-boot/model" 1205 if mode != "run" { 1206 where = fmt.Sprintf("/run/mnt/ubuntu-seed/systems/%s/model", label) 1207 } 1208 c.Assert(err, ErrorMatches, fmt.Sprintf(".*cannot read model assertion: open .*%s: no such file or directory", where)) 1209 c.Assert(measureEpochCalls, Equals, 1) 1210 c.Assert(measureModelCalls, Equals, expectedMeasureModelCalls) 1211 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 1212 gl, err := filepath.Glob(filepath.Join(dirs.SnapBootstrapRunDir, "*-model-measured")) 1213 c.Assert(err, IsNil) 1214 c.Assert(gl, HasLen, 0) 1215 } 1216 1217 func (s *initramfsMountsSuite) TestInitramfsMountsRunModeUpgradeScenarios(c *C) { 1218 tt := []struct { 1219 modeenv *boot.Modeenv 1220 // this is a function so we can have delayed execution, typical values 1221 // depend on the root dir which changes for each test case 1222 additionalMountsFunc func() []systemdMount 1223 enableKernel snap.PlaceInfo 1224 enableTryKernel snap.PlaceInfo 1225 snapFiles []snap.PlaceInfo 1226 kernelStatus string 1227 1228 expRebootPanic string 1229 expLog string 1230 expError string 1231 expModeenv *boot.Modeenv 1232 comment string 1233 }{ 1234 // default case no upgrades 1235 { 1236 modeenv: &boot.Modeenv{ 1237 Mode: "run", 1238 Base: s.core20.Filename(), 1239 CurrentKernels: []string{s.kernel.Filename()}, 1240 }, 1241 additionalMountsFunc: func() []systemdMount { 1242 return []systemdMount{ 1243 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 1244 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 1245 } 1246 }, 1247 enableKernel: s.kernel, 1248 snapFiles: []snap.PlaceInfo{s.core20, s.kernel}, 1249 comment: "happy default no upgrades", 1250 }, 1251 1252 // happy upgrade cases 1253 { 1254 modeenv: &boot.Modeenv{ 1255 Mode: "run", 1256 Base: s.core20.Filename(), 1257 CurrentKernels: []string{s.kernel.Filename(), s.kernelr2.Filename()}, 1258 }, 1259 additionalMountsFunc: func() []systemdMount { 1260 return []systemdMount{ 1261 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 1262 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernelr2), 1263 } 1264 }, 1265 kernelStatus: boot.TryingStatus, 1266 enableKernel: s.kernel, 1267 enableTryKernel: s.kernelr2, 1268 snapFiles: []snap.PlaceInfo{s.core20, s.kernel, s.kernelr2}, 1269 comment: "happy kernel snap upgrade", 1270 }, 1271 { 1272 modeenv: &boot.Modeenv{ 1273 Mode: "run", 1274 Base: s.core20.Filename(), 1275 TryBase: s.core20r2.Filename(), 1276 BaseStatus: boot.TryStatus, 1277 CurrentKernels: []string{s.kernel.Filename()}, 1278 }, 1279 additionalMountsFunc: func() []systemdMount { 1280 return []systemdMount{ 1281 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20r2), 1282 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 1283 } 1284 }, 1285 enableKernel: s.kernel, 1286 snapFiles: []snap.PlaceInfo{s.kernel, s.core20, s.core20r2}, 1287 expModeenv: &boot.Modeenv{ 1288 Mode: "run", 1289 Base: s.core20.Filename(), 1290 TryBase: s.core20r2.Filename(), 1291 BaseStatus: boot.TryingStatus, 1292 CurrentKernels: []string{s.kernel.Filename()}, 1293 }, 1294 comment: "happy base snap upgrade", 1295 }, 1296 { 1297 modeenv: &boot.Modeenv{ 1298 Mode: "run", 1299 Base: s.core20.Filename(), 1300 TryBase: s.core20r2.Filename(), 1301 BaseStatus: boot.TryStatus, 1302 CurrentKernels: []string{s.kernel.Filename(), s.kernelr2.Filename()}, 1303 }, 1304 additionalMountsFunc: func() []systemdMount { 1305 return []systemdMount{ 1306 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20r2), 1307 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernelr2), 1308 } 1309 }, 1310 enableKernel: s.kernel, 1311 enableTryKernel: s.kernelr2, 1312 snapFiles: []snap.PlaceInfo{s.kernel, s.kernelr2, s.core20, s.core20r2}, 1313 kernelStatus: boot.TryingStatus, 1314 expModeenv: &boot.Modeenv{ 1315 Mode: "run", 1316 Base: s.core20.Filename(), 1317 TryBase: s.core20r2.Filename(), 1318 BaseStatus: boot.TryingStatus, 1319 CurrentKernels: []string{s.kernel.Filename(), s.kernelr2.Filename()}, 1320 }, 1321 comment: "happy simultaneous base snap and kernel snap upgrade", 1322 }, 1323 1324 // fallback cases 1325 { 1326 modeenv: &boot.Modeenv{ 1327 Mode: "run", 1328 Base: s.core20.Filename(), 1329 TryBase: s.core20r2.Filename(), 1330 BaseStatus: boot.TryStatus, 1331 CurrentKernels: []string{s.kernel.Filename()}, 1332 }, 1333 additionalMountsFunc: func() []systemdMount { 1334 return []systemdMount{ 1335 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 1336 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 1337 } 1338 }, 1339 enableKernel: s.kernel, 1340 snapFiles: []snap.PlaceInfo{s.kernel, s.core20}, 1341 comment: "happy fallback try base not existing", 1342 }, 1343 { 1344 modeenv: &boot.Modeenv{ 1345 Mode: "run", 1346 Base: s.core20.Filename(), 1347 BaseStatus: boot.TryStatus, 1348 TryBase: "", 1349 CurrentKernels: []string{s.kernel.Filename()}, 1350 }, 1351 additionalMountsFunc: func() []systemdMount { 1352 return []systemdMount{ 1353 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 1354 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 1355 } 1356 }, 1357 enableKernel: s.kernel, 1358 snapFiles: []snap.PlaceInfo{s.kernel, s.core20}, 1359 comment: "happy fallback base_status try, empty try_base", 1360 }, 1361 { 1362 modeenv: &boot.Modeenv{ 1363 Mode: "run", 1364 Base: s.core20.Filename(), 1365 TryBase: s.core20r2.Filename(), 1366 BaseStatus: boot.TryingStatus, 1367 CurrentKernels: []string{s.kernel.Filename()}, 1368 }, 1369 additionalMountsFunc: func() []systemdMount { 1370 return []systemdMount{ 1371 s.makeRunSnapSystemdMount(snap.TypeBase, s.core20), 1372 s.makeRunSnapSystemdMount(snap.TypeKernel, s.kernel), 1373 } 1374 }, 1375 enableKernel: s.kernel, 1376 snapFiles: []snap.PlaceInfo{s.kernel, s.core20, s.core20r2}, 1377 expModeenv: &boot.Modeenv{ 1378 Mode: "run", 1379 Base: s.core20.Filename(), 1380 TryBase: s.core20r2.Filename(), 1381 BaseStatus: boot.DefaultStatus, 1382 CurrentKernels: []string{s.kernel.Filename()}, 1383 }, 1384 comment: "happy fallback failed boot with try snap", 1385 }, 1386 { 1387 modeenv: &boot.Modeenv{ 1388 Mode: "run", 1389 Base: s.core20.Filename(), 1390 CurrentKernels: []string{s.kernel.Filename()}, 1391 }, 1392 enableKernel: s.kernel, 1393 enableTryKernel: s.kernelr2, 1394 snapFiles: []snap.PlaceInfo{s.core20, s.kernel, s.kernelr2}, 1395 kernelStatus: boot.TryingStatus, 1396 expRebootPanic: "reboot due to untrusted try kernel snap", 1397 comment: "happy fallback untrusted try kernel snap", 1398 }, 1399 // TODO:UC20: if we ever have a way to compare what kernel was booted, 1400 // and we compute that the booted kernel was the try kernel, 1401 // but the try kernel is not enabled on the bootloader 1402 // (somehow??), then this should become a reboot case rather 1403 // than mount the old kernel snap 1404 { 1405 modeenv: &boot.Modeenv{ 1406 Mode: "run", 1407 Base: s.core20.Filename(), 1408 CurrentKernels: []string{s.kernel.Filename()}, 1409 }, 1410 kernelStatus: boot.TryingStatus, 1411 enableKernel: s.kernel, 1412 snapFiles: []snap.PlaceInfo{s.core20, s.kernel}, 1413 expRebootPanic: "reboot due to no try kernel snap", 1414 comment: "happy fallback kernel_status trying no try kernel", 1415 }, 1416 1417 // unhappy cases 1418 { 1419 modeenv: &boot.Modeenv{ 1420 Mode: "run", 1421 }, 1422 expError: "fallback base snap unusable: cannot get snap revision: modeenv base boot variable is empty", 1423 comment: "unhappy empty modeenv", 1424 }, 1425 // TODO:UC20: in this case snap-bootstrap should request a reboot, since we 1426 // already booted the try snap, so mounting the fallback kernel will 1427 // not match in some cases 1428 { 1429 modeenv: &boot.Modeenv{ 1430 Mode: "run", 1431 Base: s.core20.Filename(), 1432 CurrentKernels: []string{s.kernel.Filename()}, 1433 }, 1434 enableKernel: s.kernelr2, 1435 snapFiles: []snap.PlaceInfo{s.core20, s.kernelr2}, 1436 expError: fmt.Sprintf("fallback kernel snap %q is not trusted in the modeenv", s.kernelr2.Filename()), 1437 comment: "unhappy untrusted main kernel snap", 1438 }, 1439 } 1440 1441 s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") 1442 1443 for _, t := range tt { 1444 comment := Commentf(t.comment) 1445 1446 var cleanups []func() 1447 1448 if t.expRebootPanic != "" { 1449 r := boot.MockInitramfsReboot(func() error { 1450 panic(t.expRebootPanic) 1451 }) 1452 cleanups = append(cleanups, r) 1453 } 1454 1455 // setup unique root dir per test 1456 rootDir := c.MkDir() 1457 cleanups = append(cleanups, func() { dirs.SetRootDir(dirs.GlobalRootDir) }) 1458 dirs.SetRootDir(rootDir) 1459 1460 restore := disks.MockMountPointDisksToPartitionMapping( 1461 map[disks.Mountpoint]*disks.MockDiskMapping{ 1462 {Mountpoint: boot.InitramfsUbuntuBootDir}: defaultBootDisk, 1463 {Mountpoint: boot.InitramfsDataDir}: defaultBootDisk, 1464 }, 1465 ) 1466 cleanups = append(cleanups, restore) 1467 1468 // setup expected systemd-mount calls - every test case has ubuntu-boot, 1469 // ubuntu-seed and ubuntu-data mounts because all those mounts happen 1470 // before any boot logic 1471 mnts := []systemdMount{ 1472 ubuntuLabelMount("ubuntu-boot", "run"), 1473 ubuntuPartUUIDMount("ubuntu-seed-partuuid", "run"), 1474 ubuntuPartUUIDMount("ubuntu-data-partuuid", "run"), 1475 } 1476 if t.additionalMountsFunc != nil { 1477 mnts = append(mnts, t.additionalMountsFunc()...) 1478 } 1479 cleanups = append(cleanups, s.mockSystemdMountSequence(c, mnts, comment)) 1480 1481 // mock a bootloader 1482 bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir())) 1483 bootloader.Force(bloader) 1484 cleanups = append(cleanups, func() { bootloader.Force(nil) }) 1485 1486 if t.enableKernel != nil { 1487 // don't need to restore since each test case has a unique bloader 1488 bloader.SetEnabledKernel(t.enableKernel) 1489 } 1490 1491 if t.enableTryKernel != nil { 1492 bloader.SetEnabledTryKernel(t.enableTryKernel) 1493 } 1494 1495 // set the kernel_status boot var 1496 err := bloader.SetBootVars(map[string]string{"kernel_status": t.kernelStatus}) 1497 c.Assert(err, IsNil, comment) 1498 1499 // write the initial modeenv 1500 err = t.modeenv.WriteTo(boot.InitramfsWritableDir) 1501 c.Assert(err, IsNil, comment) 1502 1503 // make the snap files - no restore needed because we use a unique root 1504 // dir for each test case 1505 makeSnapFilesOnEarlyBootUbuntuData(c, t.snapFiles...) 1506 1507 if t.expRebootPanic != "" { 1508 f := func() { main.Parser().ParseArgs([]string{"initramfs-mounts"}) } 1509 c.Assert(f, PanicMatches, t.expRebootPanic, comment) 1510 } else { 1511 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 1512 if t.expError != "" { 1513 c.Assert(err, ErrorMatches, t.expError, comment) 1514 } else { 1515 c.Assert(err, IsNil, comment) 1516 1517 // check the resultant modeenv 1518 // if the expModeenv is nil, we just compare to the start 1519 newModeenv, err := boot.ReadModeenv(boot.InitramfsWritableDir) 1520 c.Assert(err, IsNil, comment) 1521 m := t.modeenv 1522 if t.expModeenv != nil { 1523 m = t.expModeenv 1524 } 1525 c.Assert(newModeenv.BaseStatus, DeepEquals, m.BaseStatus, comment) 1526 c.Assert(newModeenv.TryBase, DeepEquals, m.TryBase, comment) 1527 c.Assert(newModeenv.Base, DeepEquals, m.Base, comment) 1528 } 1529 } 1530 1531 for _, r := range cleanups { 1532 r() 1533 } 1534 } 1535 } 1536 1537 func (s *initramfsMountsSuite) testRecoverModeHappy(c *C) { 1538 // mock various files that are copied around during recover mode (and files 1539 // that shouldn't be copied around) 1540 ephemeralUbuntuData := filepath.Join(boot.InitramfsRunMntDir, "data/") 1541 err := os.MkdirAll(ephemeralUbuntuData, 0755) 1542 c.Assert(err, IsNil) 1543 // mock a auth data in the host's ubuntu-data 1544 hostUbuntuData := filepath.Join(boot.InitramfsRunMntDir, "host/ubuntu-data/") 1545 err = os.MkdirAll(hostUbuntuData, 0755) 1546 c.Assert(err, IsNil) 1547 mockCopiedFiles := []string{ 1548 // extrausers 1549 "system-data/var/lib/extrausers/passwd", 1550 "system-data/var/lib/extrausers/shadow", 1551 "system-data/var/lib/extrausers/group", 1552 "system-data/var/lib/extrausers/gshadow", 1553 // sshd 1554 "system-data/etc/ssh/ssh_host_rsa.key", 1555 "system-data/etc/ssh/ssh_host_rsa.key.pub", 1556 // user ssh 1557 "user-data/user1/.ssh/authorized_keys", 1558 "user-data/user2/.ssh/authorized_keys", 1559 // user snap authentication 1560 "user-data/user1/.snap/auth.json", 1561 // sudoers 1562 "system-data/etc/sudoers.d/create-user-test", 1563 // netplan networking 1564 "system-data/etc/netplan/00-snapd-config.yaml", // example console-conf filename 1565 "system-data/etc/netplan/50-cloud-init.yaml", // example cloud-init filename 1566 // systemd clock file 1567 "system-data/var/lib/systemd/timesync/clock", 1568 } 1569 mockUnrelatedFiles := []string{ 1570 "system-data/var/lib/foo", 1571 "system-data/etc/passwd", 1572 "user-data/user1/some-random-data", 1573 "user-data/user2/other-random-data", 1574 "user-data/user2/.snap/sneaky-not-auth.json", 1575 "system-data/etc/not-networking/netplan", 1576 "system-data/var/lib/systemd/timesync/clock-not-the-clock", 1577 } 1578 for _, mockFile := range append(mockCopiedFiles, mockUnrelatedFiles...) { 1579 p := filepath.Join(hostUbuntuData, mockFile) 1580 err = os.MkdirAll(filepath.Dir(p), 0750) 1581 c.Assert(err, IsNil) 1582 mockContent := fmt.Sprintf("content of %s", filepath.Base(mockFile)) 1583 err = ioutil.WriteFile(p, []byte(mockContent), 0640) 1584 c.Assert(err, IsNil) 1585 } 1586 // create a mock state 1587 mockedState := filepath.Join(hostUbuntuData, "system-data/var/lib/snapd/state.json") 1588 err = os.MkdirAll(filepath.Dir(mockedState), 0750) 1589 c.Assert(err, IsNil) 1590 err = ioutil.WriteFile(mockedState, []byte(mockStateContent), 0640) 1591 c.Assert(err, IsNil) 1592 1593 _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"}) 1594 c.Assert(err, IsNil) 1595 1596 modeEnv := filepath.Join(ephemeralUbuntuData, "/system-data/var/lib/snapd/modeenv") 1597 c.Check(modeEnv, testutil.FileEquals, `mode=recover 1598 recovery_system=20191118 1599 `) 1600 for _, p := range mockUnrelatedFiles { 1601 c.Check(filepath.Join(ephemeralUbuntuData, p), testutil.FileAbsent) 1602 } 1603 for _, p := range mockCopiedFiles { 1604 c.Check(filepath.Join(ephemeralUbuntuData, p), testutil.FilePresent) 1605 fi, err := os.Stat(filepath.Join(ephemeralUbuntuData, p)) 1606 // check file mode is set 1607 c.Assert(err, IsNil) 1608 c.Check(fi.Mode(), Equals, os.FileMode(0640)) 1609 // check dir mode is set in parent dir 1610 fiParent, err := os.Stat(filepath.Dir(filepath.Join(ephemeralUbuntuData, p))) 1611 c.Assert(err, IsNil) 1612 c.Check(fiParent.Mode(), Equals, os.FileMode(os.ModeDir|0750)) 1613 } 1614 1615 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}`) 1616 1617 // finally check that the recovery system bootenv was updated to be in run 1618 // mode 1619 bloader, err := bootloader.Find("", nil) 1620 c.Assert(err, IsNil) 1621 m, err := bloader.GetBootVars("snapd_recovery_system", "snapd_recovery_mode") 1622 c.Assert(err, IsNil) 1623 c.Assert(m, DeepEquals, map[string]string{ 1624 "snapd_recovery_system": "20191118", 1625 "snapd_recovery_mode": "run", 1626 }) 1627 } 1628 1629 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeHappy(c *C) { 1630 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 1631 1632 // setup a bootloader for setting the bootenv after we are done 1633 bloader := bootloadertest.Mock("mock", c.MkDir()) 1634 bootloader.Force(bloader) 1635 defer bootloader.Force(nil) 1636 1637 // mock that we don't know which partition uuid the kernel was booted from 1638 restore := main.MockPartitionUUIDForBootedKernelDisk("") 1639 defer restore() 1640 1641 restore = disks.MockMountPointDisksToPartitionMapping( 1642 map[disks.Mountpoint]*disks.MockDiskMapping{ 1643 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultBootDisk, 1644 {Mountpoint: boot.InitramfsHostUbuntuDataDir}: defaultBootDisk, 1645 }, 1646 ) 1647 defer restore() 1648 1649 restore = s.mockSystemdMountSequence(c, []systemdMount{ 1650 ubuntuLabelMount("ubuntu-seed", "recover"), 1651 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 1652 s.makeSeedSnapSystemdMount(snap.TypeKernel), 1653 s.makeSeedSnapSystemdMount(snap.TypeBase), 1654 { 1655 "tmpfs", 1656 boot.InitramfsDataDir, 1657 tmpfsMountOpts, 1658 }, 1659 { 1660 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 1661 boot.InitramfsHostUbuntuDataDir, 1662 nil, 1663 }, 1664 }, nil) 1665 defer restore() 1666 1667 s.testRecoverModeHappy(c) 1668 } 1669 1670 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeHappyBootedKernelPartitionUUID(c *C) { 1671 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 1672 1673 restore := main.MockPartitionUUIDForBootedKernelDisk("specific-ubuntu-seed-partuuid") 1674 defer restore() 1675 1676 // setup a bootloader for setting the bootenv after we are done 1677 bloader := bootloadertest.Mock("mock", c.MkDir()) 1678 bootloader.Force(bloader) 1679 defer bootloader.Force(nil) 1680 1681 restore = disks.MockMountPointDisksToPartitionMapping( 1682 map[disks.Mountpoint]*disks.MockDiskMapping{ 1683 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultBootDisk, 1684 {Mountpoint: boot.InitramfsHostUbuntuDataDir}: defaultBootDisk, 1685 }, 1686 ) 1687 defer restore() 1688 1689 restore = s.mockSystemdMountSequence(c, []systemdMount{ 1690 { 1691 "/dev/disk/by-partuuid/specific-ubuntu-seed-partuuid", 1692 boot.InitramfsUbuntuSeedDir, 1693 needsFsckDiskMountOpts, 1694 }, 1695 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 1696 s.makeSeedSnapSystemdMount(snap.TypeKernel), 1697 s.makeSeedSnapSystemdMount(snap.TypeBase), 1698 { 1699 "tmpfs", 1700 boot.InitramfsDataDir, 1701 tmpfsMountOpts, 1702 }, 1703 { 1704 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 1705 boot.InitramfsHostUbuntuDataDir, 1706 nil, 1707 }, 1708 }, nil) 1709 defer restore() 1710 1711 s.testRecoverModeHappy(c) 1712 } 1713 1714 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeHappyEncrypted(c *C) { 1715 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 1716 1717 restore := main.MockPartitionUUIDForBootedKernelDisk("") 1718 defer restore() 1719 1720 // setup a bootloader for setting the bootenv after we are done 1721 bloader := bootloadertest.Mock("mock", c.MkDir()) 1722 bootloader.Force(bloader) 1723 defer bootloader.Force(nil) 1724 1725 restore = disks.MockMountPointDisksToPartitionMapping( 1726 map[disks.Mountpoint]*disks.MockDiskMapping{ 1727 {Mountpoint: boot.InitramfsUbuntuSeedDir}: defaultEncBootDisk, 1728 { 1729 Mountpoint: boot.InitramfsHostUbuntuDataDir, 1730 IsDecryptedDevice: true, 1731 }: defaultEncBootDisk, 1732 }, 1733 ) 1734 defer restore() 1735 1736 activated := false 1737 restore = main.MockSecbootUnlockVolumeIfEncrypted(func(disk disks.Disk, name string, encryptionKeyDir string, lockKeysOnFinish bool) (string, bool, error) { 1738 c.Assert(name, Equals, "ubuntu-data") 1739 c.Assert(encryptionKeyDir, Equals, filepath.Join(s.tmpDir, "run/mnt/ubuntu-seed/device/fde")) 1740 encDevPartUUID, err := disk.FindMatchingPartitionUUID(name + "-enc") 1741 c.Assert(err, IsNil) 1742 c.Assert(encDevPartUUID, Equals, "ubuntu-data-enc-partuuid") 1743 c.Assert(lockKeysOnFinish, Equals, true) 1744 activated = true 1745 return filepath.Join("/dev/disk/by-partuuid", encDevPartUUID), true, nil 1746 }) 1747 defer restore() 1748 1749 measureEpochCalls := 0 1750 measureModelCalls := 0 1751 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 1752 measureEpochCalls++ 1753 return nil 1754 }) 1755 defer restore() 1756 1757 var measuredModel *asserts.Model 1758 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 1759 measureModelCalls++ 1760 var err error 1761 measuredModel, err = findModel() 1762 if err != nil { 1763 return err 1764 } 1765 return nil 1766 }) 1767 defer restore() 1768 1769 restore = s.mockSystemdMountSequence(c, []systemdMount{ 1770 ubuntuLabelMount("ubuntu-seed", "recover"), 1771 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 1772 s.makeSeedSnapSystemdMount(snap.TypeKernel), 1773 s.makeSeedSnapSystemdMount(snap.TypeBase), 1774 { 1775 "tmpfs", 1776 boot.InitramfsDataDir, 1777 tmpfsMountOpts, 1778 }, 1779 { 1780 "/dev/disk/by-partuuid/ubuntu-data-enc-partuuid", 1781 boot.InitramfsHostUbuntuDataDir, 1782 nil, 1783 }, 1784 }, nil) 1785 defer restore() 1786 1787 s.testRecoverModeHappy(c) 1788 1789 c.Check(activated, Equals, true) 1790 c.Check(measureEpochCalls, Equals, 1) 1791 c.Check(measureModelCalls, Equals, 1) 1792 c.Check(measuredModel, DeepEquals, s.model) 1793 1794 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 1795 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent) 1796 } 1797 1798 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedAttackerFSAttachedHappy(c *C) { 1799 s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel) 1800 1801 restore := main.MockPartitionUUIDForBootedKernelDisk("") 1802 defer restore() 1803 1804 // setup a bootloader for setting the bootenv 1805 bloader := bootloadertest.Mock("mock", c.MkDir()) 1806 bootloader.Force(bloader) 1807 defer bootloader.Force(nil) 1808 1809 mockDisk := &disks.MockDiskMapping{ 1810 FilesystemLabelToPartUUID: map[string]string{ 1811 "ubuntu-seed": "ubuntu-seed-partuuid", 1812 "ubuntu-data-enc": "ubuntu-data-enc-partuuid", 1813 }, 1814 DiskHasPartitions: true, 1815 DevNum: "bootDev", 1816 } 1817 1818 restore = disks.MockMountPointDisksToPartitionMapping( 1819 map[disks.Mountpoint]*disks.MockDiskMapping{ 1820 {Mountpoint: boot.InitramfsUbuntuSeedDir}: mockDisk, 1821 { 1822 Mountpoint: boot.InitramfsHostUbuntuDataDir, 1823 IsDecryptedDevice: true, 1824 }: mockDisk, 1825 // this is the attacker fs on a different disk 1826 {Mountpoint: "somewhere-else"}: { 1827 FilesystemLabelToPartUUID: map[string]string{ 1828 "ubuntu-seed": "ubuntu-seed-attacker-partuuid", 1829 "ubuntu-data-enc": "ubuntu-data-enc-attacker-partuuid", 1830 }, 1831 DiskHasPartitions: true, 1832 DevNum: "attackerDev", 1833 }, 1834 }, 1835 ) 1836 defer restore() 1837 1838 activated := false 1839 restore = main.MockSecbootUnlockVolumeIfEncrypted(func(disk disks.Disk, name string, encryptionKeyDir string, lockKeysOnFinish bool) (string, bool, error) { 1840 c.Assert(name, Equals, "ubuntu-data") 1841 encDevPartUUID, err := disk.FindMatchingPartitionUUID(name + "-enc") 1842 c.Assert(err, IsNil) 1843 c.Assert(encDevPartUUID, Equals, "ubuntu-data-enc-partuuid") 1844 c.Assert(lockKeysOnFinish, Equals, true) 1845 activated = true 1846 return filepath.Join("/dev/disk/by-partuuid", encDevPartUUID), true, nil 1847 }) 1848 defer restore() 1849 1850 measureEpochCalls := 0 1851 measureModelCalls := 0 1852 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 1853 measureEpochCalls++ 1854 return nil 1855 }) 1856 defer restore() 1857 1858 var measuredModel *asserts.Model 1859 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 1860 measureModelCalls++ 1861 var err error 1862 measuredModel, err = findModel() 1863 if err != nil { 1864 return err 1865 } 1866 return nil 1867 }) 1868 defer restore() 1869 1870 restore = s.mockSystemdMountSequence(c, []systemdMount{ 1871 ubuntuLabelMount("ubuntu-seed", "recover"), 1872 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 1873 s.makeSeedSnapSystemdMount(snap.TypeKernel), 1874 s.makeSeedSnapSystemdMount(snap.TypeBase), 1875 { 1876 "tmpfs", 1877 boot.InitramfsDataDir, 1878 tmpfsMountOpts, 1879 }, 1880 { 1881 "/dev/disk/by-partuuid/ubuntu-data-enc-partuuid", 1882 boot.InitramfsHostUbuntuDataDir, 1883 nil, 1884 }, 1885 }, nil) 1886 defer restore() 1887 1888 s.testRecoverModeHappy(c) 1889 1890 c.Check(activated, Equals, true) 1891 c.Check(measureEpochCalls, Equals, 1) 1892 c.Check(measureModelCalls, Equals, 1) 1893 c.Check(measuredModel, DeepEquals, s.model) 1894 1895 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 1896 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent) 1897 } 1898 1899 func (s *initramfsMountsSuite) testInitramfsMountsInstallRecoverModeMeasure(c *C, mode string) { 1900 s.mockProcCmdlineContent(c, fmt.Sprintf("snapd_recovery_mode=%s snapd_recovery_system=%s", mode, s.sysLabel)) 1901 1902 modeMnts := []systemdMount{ 1903 ubuntuLabelMount("ubuntu-seed", mode), 1904 s.makeSeedSnapSystemdMount(snap.TypeSnapd), 1905 s.makeSeedSnapSystemdMount(snap.TypeKernel), 1906 s.makeSeedSnapSystemdMount(snap.TypeBase), 1907 { 1908 "tmpfs", 1909 boot.InitramfsDataDir, 1910 tmpfsMountOpts, 1911 }, 1912 } 1913 1914 mockDiskMapping := map[disks.Mountpoint]*disks.MockDiskMapping{ 1915 {Mountpoint: boot.InitramfsUbuntuSeedDir}: { 1916 FilesystemLabelToPartUUID: map[string]string{ 1917 "ubuntu-seed": "ubuntu-seed-partuuid", 1918 }, 1919 DiskHasPartitions: true, 1920 }, 1921 } 1922 1923 if mode == "recover" { 1924 // setup a bootloader for setting the bootenv after we are done 1925 bloader := bootloadertest.Mock("mock", c.MkDir()) 1926 bootloader.Force(bloader) 1927 defer bootloader.Force(nil) 1928 1929 // add the expected mount of ubuntu-data onto the host data dir 1930 modeMnts = append(modeMnts, systemdMount{ 1931 "/dev/disk/by-partuuid/ubuntu-data-partuuid", 1932 boot.InitramfsHostUbuntuDataDir, 1933 nil, 1934 }) 1935 1936 // also add the ubuntu-data fs label to the disk referenced by the 1937 // ubuntu-seed partition 1938 disk := mockDiskMapping[disks.Mountpoint{Mountpoint: boot.InitramfsUbuntuSeedDir}] 1939 disk.FilesystemLabelToPartUUID["ubuntu-data"] = "ubuntu-data-partuuid" 1940 1941 // and also add the /run/mnt/host/ubuntu-data mountpoint for 1942 // cross-checking after it is mounted 1943 mockDiskMapping[disks.Mountpoint{Mountpoint: boot.InitramfsHostUbuntuDataDir}] = disk 1944 } 1945 1946 restore := disks.MockMountPointDisksToPartitionMapping(mockDiskMapping) 1947 defer restore() 1948 1949 measureEpochCalls := 0 1950 measureModelCalls := 0 1951 restore = main.MockSecbootMeasureSnapSystemEpochWhenPossible(func() error { 1952 measureEpochCalls++ 1953 return nil 1954 }) 1955 defer restore() 1956 1957 var measuredModel *asserts.Model 1958 restore = main.MockSecbootMeasureSnapModelWhenPossible(func(findModel func() (*asserts.Model, error)) error { 1959 measureModelCalls++ 1960 var err error 1961 measuredModel, err = findModel() 1962 if err != nil { 1963 return err 1964 } 1965 return nil 1966 }) 1967 defer restore() 1968 1969 restore = s.mockSystemdMountSequence(c, modeMnts, nil) 1970 defer restore() 1971 1972 if mode == "recover" { 1973 // use the helper 1974 s.testRecoverModeHappy(c) 1975 } else { 1976 _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"}) 1977 c.Assert(err, IsNil) 1978 1979 modeEnv := filepath.Join(boot.InitramfsDataDir, "/system-data/var/lib/snapd/modeenv") 1980 c.Check(modeEnv, testutil.FileEquals, `mode=install 1981 recovery_system=20191118 1982 `) 1983 } 1984 1985 c.Check(measuredModel, NotNil) 1986 c.Check(measuredModel, DeepEquals, s.model) 1987 c.Check(measureEpochCalls, Equals, 1) 1988 c.Check(measureModelCalls, Equals, 1) 1989 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent) 1990 c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, s.sysLabel+"-model-measured"), testutil.FilePresent) 1991 } 1992 1993 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeMeasure(c *C) { 1994 s.testInitramfsMountsInstallRecoverModeMeasure(c, "install") 1995 } 1996 1997 func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeUnsetMeasure(c *C) { 1998 // TODO:UC20: eventually we should require snapd_recovery_mode to be set to 1999 // explicitly "install" for install mode, but we originally allowed 2000 // snapd_recovery_mode="" and interpreted it as install mode, so test that 2001 // case too 2002 s.testInitramfsMountsInstallRecoverModeMeasure(c, "") 2003 } 2004 2005 func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeMeasure(c *C) { 2006 s.testInitramfsMountsInstallRecoverModeMeasure(c, "recover") 2007 }