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