gitee.com/mysnapcore/mysnapd@v0.1.0/boot/makebootable_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2022 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package boot_test 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 28 . "gopkg.in/check.v1" 29 30 "gitee.com/mysnapcore/mysnapd/arch/archtest" 31 "gitee.com/mysnapcore/mysnapd/asserts" 32 "gitee.com/mysnapcore/mysnapd/boot" 33 "gitee.com/mysnapcore/mysnapd/boot/boottest" 34 "gitee.com/mysnapcore/mysnapd/bootloader" 35 "gitee.com/mysnapcore/mysnapd/bootloader/assets" 36 "gitee.com/mysnapcore/mysnapd/bootloader/bootloadertest" 37 "gitee.com/mysnapcore/mysnapd/bootloader/grubenv" 38 "gitee.com/mysnapcore/mysnapd/bootloader/ubootenv" 39 "gitee.com/mysnapcore/mysnapd/dirs" 40 "gitee.com/mysnapcore/mysnapd/gadget" 41 "gitee.com/mysnapcore/mysnapd/osutil" 42 "gitee.com/mysnapcore/mysnapd/release" 43 "gitee.com/mysnapcore/mysnapd/secboot" 44 "gitee.com/mysnapcore/mysnapd/secboot/keys" 45 "gitee.com/mysnapcore/mysnapd/seed" 46 "gitee.com/mysnapcore/mysnapd/snap" 47 "gitee.com/mysnapcore/mysnapd/snap/snapfile" 48 "gitee.com/mysnapcore/mysnapd/snap/snaptest" 49 "gitee.com/mysnapcore/mysnapd/testutil" 50 "gitee.com/mysnapcore/mysnapd/timings" 51 ) 52 53 type makeBootableSuite struct { 54 baseBootenvSuite 55 56 bootloader *bootloadertest.MockBootloader 57 } 58 59 var _ = Suite(&makeBootableSuite{}) 60 61 func (s *makeBootableSuite) SetUpTest(c *C) { 62 s.baseBootenvSuite.SetUpTest(c) 63 64 s.bootloader = bootloadertest.Mock("mock", c.MkDir()) 65 s.forceBootloader(s.bootloader) 66 67 s.AddCleanup(archtest.MockArchitecture("amd64")) 68 snippets := []assets.ForEditions{ 69 {FirstEdition: 1, Snippet: []byte("console=ttyS0 console=tty1 panic=-1")}, 70 } 71 s.AddCleanup(assets.MockSnippetsForEdition("grub.cfg:static-cmdline", snippets)) 72 s.AddCleanup(assets.MockSnippetsForEdition("grub-recovery.cfg:static-cmdline", snippets)) 73 } 74 75 func makeSnap(c *C, name, yaml string, revno snap.Revision) (fn string, info *snap.Info) { 76 return makeSnapWithFiles(c, name, yaml, revno, nil) 77 } 78 79 func makeSnapWithFiles(c *C, name, yaml string, revno snap.Revision, files [][]string) (fn string, info *snap.Info) { 80 si := &snap.SideInfo{ 81 RealName: name, 82 Revision: revno, 83 } 84 fn = snaptest.MakeTestSnapWithFiles(c, yaml, files) 85 snapf, err := snapfile.Open(fn) 86 c.Assert(err, IsNil) 87 info, err = snap.ReadInfoFromSnapFile(snapf, si) 88 c.Assert(err, IsNil) 89 return fn, info 90 } 91 92 func (s *makeBootableSuite) TestMakeBootableImage(c *C) { 93 bootloader.Force(nil) 94 model := boottest.MakeMockModel() 95 96 grubCfg := []byte("#grub cfg") 97 unpackedGadgetDir := c.MkDir() 98 err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644) 99 c.Assert(err, IsNil) 100 101 seedSnapsDirs := filepath.Join(s.rootdir, "/var/lib/snapd/seed", "snaps") 102 err = os.MkdirAll(seedSnapsDirs, 0755) 103 c.Assert(err, IsNil) 104 105 baseFn, baseInfo := makeSnap(c, "core18", `name: core18 106 type: base 107 version: 4.0 108 `, snap.R(3)) 109 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 110 err = os.Rename(baseFn, baseInSeed) 111 c.Assert(err, IsNil) 112 kernelFn, kernelInfo := makeSnap(c, "pc-kernel", `name: pc-kernel 113 type: kernel 114 version: 4.0 115 `, snap.R(5)) 116 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 117 err = os.Rename(kernelFn, kernelInSeed) 118 c.Assert(err, IsNil) 119 120 bootWith := &boot.BootableSet{ 121 Base: baseInfo, 122 BasePath: baseInSeed, 123 Kernel: kernelInfo, 124 KernelPath: kernelInSeed, 125 UnpackedGadgetDir: unpackedGadgetDir, 126 } 127 128 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 129 c.Assert(err, IsNil) 130 131 // check the bootloader config 132 seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "boot/grub/grubenv")) 133 c.Assert(seedGenv.Load(), IsNil) 134 c.Check(seedGenv.Get("snap_kernel"), Equals, "pc-kernel_5.snap") 135 c.Check(seedGenv.Get("snap_core"), Equals, "core18_3.snap") 136 c.Check(seedGenv.Get("snap_menuentry"), Equals, "My Model") 137 138 // check symlinks from snap blob dir 139 kernelBlob := filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), kernelInfo.Filename()) 140 dst, err := os.Readlink(filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), kernelInfo.Filename())) 141 c.Assert(err, IsNil) 142 c.Check(dst, Equals, "../seed/snaps/pc-kernel_5.snap") 143 c.Check(kernelBlob, testutil.FilePresent) 144 145 baseBlob := filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), baseInfo.Filename()) 146 dst, err = os.Readlink(filepath.Join(dirs.SnapBlobDirUnder(s.rootdir), baseInfo.Filename())) 147 c.Assert(err, IsNil) 148 c.Check(dst, Equals, "../seed/snaps/core18_3.snap") 149 c.Check(baseBlob, testutil.FilePresent) 150 151 // check that the bootloader (grub here) configuration was copied 152 c.Check(filepath.Join(s.rootdir, "boot", "grub/grub.cfg"), testutil.FileEquals, grubCfg) 153 } 154 155 type makeBootable20Suite struct { 156 baseBootenvSuite 157 158 bootloader *bootloadertest.MockRecoveryAwareBootloader 159 } 160 161 type makeBootable20UbootSuite struct { 162 baseBootenvSuite 163 164 bootloader *bootloadertest.MockExtractedRecoveryKernelImageBootloader 165 } 166 167 var _ = Suite(&makeBootable20Suite{}) 168 var _ = Suite(&makeBootable20UbootSuite{}) 169 170 func (s *makeBootable20Suite) SetUpTest(c *C) { 171 s.baseBootenvSuite.SetUpTest(c) 172 173 s.bootloader = bootloadertest.Mock("mock", c.MkDir()).RecoveryAware() 174 s.forceBootloader(s.bootloader) 175 s.AddCleanup(archtest.MockArchitecture("amd64")) 176 snippets := []assets.ForEditions{ 177 {FirstEdition: 1, Snippet: []byte("console=ttyS0 console=tty1 panic=-1")}, 178 } 179 s.AddCleanup(assets.MockSnippetsForEdition("grub.cfg:static-cmdline", snippets)) 180 s.AddCleanup(assets.MockSnippetsForEdition("grub-recovery.cfg:static-cmdline", snippets)) 181 } 182 183 func (s *makeBootable20UbootSuite) SetUpTest(c *C) { 184 s.baseBootenvSuite.SetUpTest(c) 185 186 s.bootloader = bootloadertest.Mock("mock", c.MkDir()).ExtractedRecoveryKernelImage() 187 s.forceBootloader(s.bootloader) 188 } 189 190 func (s *makeBootable20Suite) TestMakeBootableImage20(c *C) { 191 bootloader.Force(nil) 192 model := boottest.MakeMockUC20Model() 193 194 unpackedGadgetDir := c.MkDir() 195 grubRecoveryCfg := "#grub-recovery cfg" 196 grubRecoveryCfgAsset := "#grub-recovery cfg from assets" 197 grubCfg := "#grub cfg" 198 snaptest.PopulateDir(unpackedGadgetDir, [][]string{ 199 {"grub-recovery.conf", grubRecoveryCfg}, 200 {"grub.conf", grubCfg}, 201 {"meta/snap.yaml", gadgetSnapYaml}, 202 }) 203 restore := assets.MockInternal("grub-recovery.cfg", []byte(grubRecoveryCfgAsset)) 204 defer restore() 205 206 // on uc20 the seed layout if different 207 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 208 err := os.MkdirAll(seedSnapsDirs, 0755) 209 c.Assert(err, IsNil) 210 211 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 212 type: base 213 version: 5.0 214 `, snap.R(3)) 215 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 216 err = os.Rename(baseFn, baseInSeed) 217 c.Assert(err, IsNil) 218 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 219 type: kernel 220 version: 5.0 221 `, snap.R(5), [][]string{ 222 {"kernel.efi", "I'm a kernel.efi"}, 223 }) 224 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 225 err = os.Rename(kernelFn, kernelInSeed) 226 c.Assert(err, IsNil) 227 228 label := "20191209" 229 recoverySystemDir := filepath.Join("/systems", label) 230 bootWith := &boot.BootableSet{ 231 Base: baseInfo, 232 BasePath: baseInSeed, 233 Kernel: kernelInfo, 234 KernelPath: kernelInSeed, 235 RecoverySystemDir: recoverySystemDir, 236 RecoverySystemLabel: label, 237 UnpackedGadgetDir: unpackedGadgetDir, 238 Recovery: true, 239 } 240 241 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 242 c.Assert(err, IsNil) 243 244 // ensure only a single file got copied (the grub.cfg) 245 files, err := filepath.Glob(filepath.Join(s.rootdir, "EFI/ubuntu/*")) 246 c.Assert(err, IsNil) 247 // grub.cfg and grubenv 248 c.Check(files, HasLen, 2) 249 // check that the recovery bootloader configuration was installed with 250 // the correct content 251 c.Check(filepath.Join(s.rootdir, "EFI/ubuntu/grub.cfg"), testutil.FileEquals, grubRecoveryCfgAsset) 252 253 // ensure no /boot was setup 254 c.Check(filepath.Join(s.rootdir, "boot"), testutil.FileAbsent) 255 256 // ensure the correct recovery system configuration was set 257 seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv")) 258 c.Assert(seedGenv.Load(), IsNil) 259 c.Check(seedGenv.Get("snapd_recovery_system"), Equals, label) 260 261 systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv")) 262 c.Assert(systemGenv.Load(), IsNil) 263 c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap") 264 } 265 266 func (s *makeBootable20Suite) TestMakeBootableImage20BootFlags(c *C) { 267 bootloader.Force(nil) 268 model := boottest.MakeMockUC20Model() 269 270 unpackedGadgetDir := c.MkDir() 271 grubRecoveryCfg := "#grub-recovery cfg" 272 grubRecoveryCfgAsset := "#grub-recovery cfg from assets" 273 grubCfg := "#grub cfg" 274 snaptest.PopulateDir(unpackedGadgetDir, [][]string{ 275 {"grub-recovery.conf", grubRecoveryCfg}, 276 {"grub.conf", grubCfg}, 277 {"meta/snap.yaml", gadgetSnapYaml}, 278 }) 279 restore := assets.MockInternal("grub-recovery.cfg", []byte(grubRecoveryCfgAsset)) 280 defer restore() 281 282 // on uc20 the seed layout if different 283 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 284 err := os.MkdirAll(seedSnapsDirs, 0755) 285 c.Assert(err, IsNil) 286 287 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 288 type: base 289 version: 5.0 290 `, snap.R(3)) 291 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 292 err = os.Rename(baseFn, baseInSeed) 293 c.Assert(err, IsNil) 294 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 295 type: kernel 296 version: 5.0 297 `, snap.R(5), [][]string{ 298 {"kernel.efi", "I'm a kernel.efi"}, 299 }) 300 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 301 err = os.Rename(kernelFn, kernelInSeed) 302 c.Assert(err, IsNil) 303 304 label := "20191209" 305 recoverySystemDir := filepath.Join("/systems", label) 306 bootWith := &boot.BootableSet{ 307 Base: baseInfo, 308 BasePath: baseInSeed, 309 Kernel: kernelInfo, 310 KernelPath: kernelInSeed, 311 RecoverySystemDir: recoverySystemDir, 312 RecoverySystemLabel: label, 313 UnpackedGadgetDir: unpackedGadgetDir, 314 Recovery: true, 315 } 316 bootFlags := []string{"factory"} 317 318 err = boot.MakeBootableImage(model, s.rootdir, bootWith, bootFlags) 319 c.Assert(err, IsNil) 320 321 // ensure the correct recovery system configuration was set 322 seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv")) 323 c.Assert(seedGenv.Load(), IsNil) 324 c.Check(seedGenv.Get("snapd_recovery_system"), Equals, label) 325 c.Check(seedGenv.Get("snapd_boot_flags"), Equals, "factory") 326 327 systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv")) 328 c.Assert(systemGenv.Load(), IsNil) 329 c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap") 330 331 } 332 333 func (s *makeBootable20Suite) testMakeBootableImage20CustomKernelArgs(c *C, whichFile, content, errMsg string) { 334 bootloader.Force(nil) 335 model := boottest.MakeMockUC20Model() 336 337 unpackedGadgetDir := c.MkDir() 338 grubCfg := "#grub cfg" 339 snaptest.PopulateDir(unpackedGadgetDir, [][]string{ 340 {"grub.conf", grubCfg}, 341 {"meta/snap.yaml", gadgetSnapYaml}, 342 {whichFile, content}, 343 }) 344 345 // on uc20 the seed layout if different 346 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 347 err := os.MkdirAll(seedSnapsDirs, 0755) 348 c.Assert(err, IsNil) 349 350 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 351 type: base 352 version: 5.0 353 `, snap.R(3)) 354 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 355 err = os.Rename(baseFn, baseInSeed) 356 c.Assert(err, IsNil) 357 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 358 type: kernel 359 version: 5.0 360 `, snap.R(5), [][]string{ 361 {"kernel.efi", "I'm a kernel.efi"}, 362 }) 363 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 364 err = os.Rename(kernelFn, kernelInSeed) 365 c.Assert(err, IsNil) 366 367 label := "20191209" 368 recoverySystemDir := filepath.Join("/systems", label) 369 bootWith := &boot.BootableSet{ 370 Base: baseInfo, 371 BasePath: baseInSeed, 372 Kernel: kernelInfo, 373 KernelPath: kernelInSeed, 374 RecoverySystemDir: recoverySystemDir, 375 RecoverySystemLabel: label, 376 UnpackedGadgetDir: unpackedGadgetDir, 377 Recovery: true, 378 } 379 380 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 381 if errMsg != "" { 382 c.Assert(err, ErrorMatches, errMsg) 383 return 384 } 385 c.Assert(err, IsNil) 386 387 // ensure the correct recovery system configuration was set 388 seedGenv := grubenv.NewEnv(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv")) 389 c.Assert(seedGenv.Load(), IsNil) 390 c.Check(seedGenv.Get("snapd_recovery_system"), Equals, label) 391 // and kernel command line 392 systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv")) 393 c.Assert(systemGenv.Load(), IsNil) 394 c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap") 395 switch whichFile { 396 case "cmdline.extra": 397 c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, content) 398 c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, "") 399 case "cmdline.full": 400 c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, "") 401 c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, content) 402 } 403 } 404 405 func (s *makeBootable20Suite) TestMakeBootableImage20CustomKernelExtraArgs(c *C) { 406 s.testMakeBootableImage20CustomKernelArgs(c, "cmdline.extra", "foo bar baz", "") 407 } 408 409 func (s *makeBootable20Suite) TestMakeBootableImage20CustomKernelFullArgs(c *C) { 410 s.testMakeBootableImage20CustomKernelArgs(c, "cmdline.full", "foo bar baz", "") 411 } 412 413 func (s *makeBootable20Suite) TestMakeBootableImage20CustomKernelInvalidArgs(c *C) { 414 errMsg := `cannot obtain recovery system command line: cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: disallowed kernel argument "snapd_foo=bar"` 415 s.testMakeBootableImage20CustomKernelArgs(c, "cmdline.extra", "snapd_foo=bar", errMsg) 416 } 417 418 func (s *makeBootable20Suite) TestMakeBootableImage20UnsetRecoverySystemLabelError(c *C) { 419 model := boottest.MakeMockUC20Model() 420 421 unpackedGadgetDir := c.MkDir() 422 grubRecoveryCfg := []byte("#grub-recovery cfg") 423 err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub-recovery.conf"), grubRecoveryCfg, 0644) 424 c.Assert(err, IsNil) 425 grubCfg := []byte("#grub cfg") 426 err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644) 427 c.Assert(err, IsNil) 428 429 label := "20191209" 430 recoverySystemDir := filepath.Join("/systems", label) 431 bootWith := &boot.BootableSet{ 432 RecoverySystemDir: recoverySystemDir, 433 UnpackedGadgetDir: unpackedGadgetDir, 434 Recovery: true, 435 } 436 437 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 438 c.Assert(err, ErrorMatches, "internal error: recovery system label unset") 439 } 440 441 func (s *makeBootable20Suite) TestMakeBootableImage20MultipleRecoverySystemsError(c *C) { 442 model := boottest.MakeMockUC20Model() 443 444 bootWith := &boot.BootableSet{Recovery: true} 445 err := os.MkdirAll(filepath.Join(s.rootdir, "systems/20191204"), 0755) 446 c.Assert(err, IsNil) 447 err = os.MkdirAll(filepath.Join(s.rootdir, "systems/20191205"), 0755) 448 c.Assert(err, IsNil) 449 450 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 451 c.Assert(err, ErrorMatches, "cannot make multiple recovery systems bootable yet") 452 } 453 454 func (s *makeBootable20Suite) TestMakeSystemRunnable16Fails(c *C) { 455 model := boottest.MakeMockModel() 456 457 err := boot.MakeRunnableSystem(model, nil, nil) 458 c.Assert(err, ErrorMatches, `internal error: cannot make pre-UC20 system runnable`) 459 } 460 461 func (s *makeBootable20Suite) testMakeSystemRunnable20(c *C, standalone, factoryReset, classic bool) { 462 restore := release.MockOnClassic(classic) 463 defer restore() 464 dirs.SetRootDir(dirs.GlobalRootDir) 465 466 bootloader.Force(nil) 467 468 var model *asserts.Model 469 if classic { 470 model = boottest.MakeMockUC20Model(map[string]interface{}{ 471 "classic": "true", 472 "distribution": "ubuntu", 473 }) 474 } else { 475 model = boottest.MakeMockUC20Model() 476 } 477 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 478 err := os.MkdirAll(seedSnapsDirs, 0755) 479 c.Assert(err, IsNil) 480 481 // grub on ubuntu-seed 482 mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu") 483 mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg") 484 err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755) 485 c.Assert(err, IsNil) 486 err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644) 487 c.Assert(err, IsNil) 488 genv := grubenv.NewEnv(filepath.Join(mockSeedGrubDir, "grubenv")) 489 c.Assert(genv.Save(), IsNil) 490 491 // setup recovery boot assets 492 err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755) 493 c.Assert(err, IsNil) 494 // SHA3-384: 39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37 495 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"), 496 []byte("recovery shim content"), 0644) 497 c.Assert(err, IsNil) 498 // SHA3-384: aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5 499 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"), 500 []byte("recovery grub content"), 0644) 501 c.Assert(err, IsNil) 502 503 // grub on ubuntu-boot 504 mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu") 505 mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg") 506 err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755) 507 c.Assert(err, IsNil) 508 err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644) 509 c.Assert(err, IsNil) 510 511 unpackedGadgetDir := c.MkDir() 512 grubRecoveryCfg := []byte("#grub-recovery cfg") 513 grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets") 514 grubCfg := []byte("#grub cfg") 515 grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets") 516 snaptest.PopulateDir(unpackedGadgetDir, [][]string{ 517 {"grub-recovery.conf", string(grubRecoveryCfg)}, 518 {"grub.conf", string(grubCfg)}, 519 {"bootx64.efi", "shim content"}, 520 {"grubx64.efi", "grub content"}, 521 {"meta/snap.yaml", gadgetSnapYaml}, 522 }) 523 restore = assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset) 524 defer restore() 525 restore = assets.MockInternal("grub.cfg", grubCfgAsset) 526 defer restore() 527 528 // make the snaps symlinks so that we can ensure that makebootable follows 529 // the symlinks and copies the files and not the symlinks 530 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 531 type: base 532 version: 5.0 533 `, snap.R(3)) 534 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 535 err = os.Symlink(baseFn, baseInSeed) 536 c.Assert(err, IsNil) 537 gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc 538 type: gadget 539 version: 5.0 540 `, snap.R(4)) 541 gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename()) 542 err = os.Symlink(gadgetFn, gadgetInSeed) 543 c.Assert(err, IsNil) 544 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 545 type: kernel 546 version: 5.0 547 `, snap.R(5), 548 [][]string{ 549 {"kernel.efi", "I'm a kernel.efi"}, 550 }, 551 ) 552 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 553 err = os.Symlink(kernelFn, kernelInSeed) 554 c.Assert(err, IsNil) 555 556 bootWith := &boot.BootableSet{ 557 RecoverySystemLabel: "20191216", 558 BasePath: baseInSeed, 559 Base: baseInfo, 560 Gadget: gadgetInfo, 561 GadgetPath: gadgetInSeed, 562 KernelPath: kernelInSeed, 563 Kernel: kernelInfo, 564 Recovery: false, 565 UnpackedGadgetDir: unpackedGadgetDir, 566 } 567 568 // set up observer state 569 useEncryption := true 570 obs, err := boot.TrustedAssetsInstallObserverForModel(model, unpackedGadgetDir, useEncryption) 571 c.Assert(obs, NotNil) 572 c.Assert(err, IsNil) 573 runBootStruct := &gadget.LaidOutStructure{ 574 VolumeStructure: &gadget.VolumeStructure{ 575 Role: gadget.SystemBoot, 576 }, 577 } 578 579 // only grubx64.efi gets installed to system-boot 580 _, err = obs.Observe(gadget.ContentWrite, runBootStruct, boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi", 581 &gadget.ContentChange{After: filepath.Join(unpackedGadgetDir, "grubx64.efi")}) 582 c.Assert(err, IsNil) 583 584 // observe recovery assets 585 err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir) 586 c.Assert(err, IsNil) 587 588 // set encryption key 589 myKey := keys.EncryptionKey{} 590 myKey2 := keys.EncryptionKey{} 591 for i := range myKey { 592 myKey[i] = byte(i) 593 myKey2[i] = byte(128 + i) 594 } 595 obs.ChosenEncryptionKeys(myKey, myKey2) 596 597 // set a mock recovery kernel 598 readSystemEssentialCalls := 0 599 restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 600 readSystemEssentialCalls++ 601 return model, []*seed.Snap{mockKernelSeedSnap(snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil 602 }) 603 defer restore() 604 605 provisionCalls := 0 606 restore = boot.MockSecbootProvisionTPM(func(mode secboot.TPMProvisionMode, lockoutAuthFile string) error { 607 provisionCalls++ 608 c.Check(lockoutAuthFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-lockout-auth")) 609 if factoryReset { 610 c.Check(mode, Equals, secboot.TPMPartialReprovision) 611 } else { 612 c.Check(mode, Equals, secboot.TPMProvisionFull) 613 } 614 return nil 615 }) 616 defer restore() 617 618 pcrHandleOfKeyCalls := 0 619 restore = boot.MockSecbootPCRHandleOfSealedKey(func(p string) (uint32, error) { 620 pcrHandleOfKeyCalls++ 621 c.Check(provisionCalls, Equals, 0) 622 if !factoryReset { 623 c.Errorf("unexpected call in non-factory-reset scenario") 624 return 0, fmt.Errorf("unexpected call") 625 } 626 c.Check(p, Equals, 627 filepath.Join(s.rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key")) 628 // trigger use of alt handles as current key is using the main handle 629 return secboot.FallbackObjectPCRPolicyCounterHandle, nil 630 }) 631 defer restore() 632 633 releasePCRHandleCalls := 0 634 restore = boot.MockSecbootReleasePCRResourceHandles(func(handles ...uint32) error { 635 c.Check(factoryReset, Equals, true) 636 releasePCRHandleCalls++ 637 c.Check(handles, DeepEquals, []uint32{ 638 secboot.AltRunObjectPCRPolicyCounterHandle, 639 secboot.AltFallbackObjectPCRPolicyCounterHandle, 640 }) 641 return nil 642 }) 643 defer restore() 644 645 hasFDESetupHookCalled := false 646 restore = boot.MockHasFDESetupHook(func(kernel *snap.Info) (bool, error) { 647 c.Check(kernel, Equals, kernelInfo) 648 hasFDESetupHookCalled = true 649 return false, nil 650 }) 651 defer restore() 652 653 // set mock key sealing 654 sealKeysCalls := 0 655 restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error { 656 c.Assert(provisionCalls, Equals, 1, Commentf("TPM must have been provisioned before")) 657 sealKeysCalls++ 658 switch sealKeysCalls { 659 case 1: 660 c.Check(keys, HasLen, 1) 661 c.Check(keys[0].Key, DeepEquals, myKey) 662 c.Check(keys[0].KeyFile, Equals, 663 filepath.Join(s.rootdir, "/run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")) 664 if factoryReset { 665 c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltRunObjectPCRPolicyCounterHandle) 666 } else { 667 c.Check(params.PCRPolicyCounterHandle, Equals, secboot.RunObjectPCRPolicyCounterHandle) 668 } 669 case 2: 670 c.Check(keys, HasLen, 2) 671 c.Check(keys[0].Key, DeepEquals, myKey) 672 c.Check(keys[1].Key, DeepEquals, myKey2) 673 c.Check(keys[0].KeyFile, Equals, 674 filepath.Join(s.rootdir, 675 "/run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key")) 676 if factoryReset { 677 c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltFallbackObjectPCRPolicyCounterHandle) 678 c.Check(keys[1].KeyFile, Equals, 679 filepath.Join(s.rootdir, 680 "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key.factory-reset")) 681 682 } else { 683 c.Check(params.PCRPolicyCounterHandle, Equals, secboot.FallbackObjectPCRPolicyCounterHandle) 684 c.Check(keys[1].KeyFile, Equals, 685 filepath.Join(s.rootdir, 686 "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key")) 687 } 688 default: 689 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 690 } 691 c.Assert(params.ModelParams, HasLen, 1) 692 693 shim := bootloader.NewBootFile("", filepath.Join(s.rootdir, 694 "var/lib/snapd/boot-assets/grub/bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"), 695 bootloader.RoleRecovery) 696 grub := bootloader.NewBootFile("", filepath.Join(s.rootdir, 697 "var/lib/snapd/boot-assets/grub/grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"), 698 bootloader.RoleRecovery) 699 runGrub := bootloader.NewBootFile("", filepath.Join(s.rootdir, 700 "var/lib/snapd/boot-assets/grub/grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"), 701 bootloader.RoleRunMode) 702 kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 703 var runKernelPath string 704 var runKernel bootloader.BootFile 705 switch { 706 case !standalone: 707 runKernelPath = "/var/lib/snapd/snaps/pc-kernel_5.snap" 708 case classic: 709 runKernelPath = "/run/mnt/ubuntu-data/var/lib/snapd/snaps/pc-kernel_5.snap" 710 case !classic: 711 runKernelPath = "/run/mnt/ubuntu-data/system-data/var/lib/snapd/snaps/pc-kernel_5.snap" 712 } 713 runKernel = bootloader.NewBootFile(filepath.Join(s.rootdir, runKernelPath), "kernel.efi", bootloader.RoleRunMode) 714 switch sealKeysCalls { 715 case 1: 716 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 717 secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))), 718 secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(runGrub, secboot.NewLoadChain(runKernel)))), 719 }) 720 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 721 "snapd_recovery_mode=factory-reset snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1", 722 "snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1", 723 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 724 }) 725 case 2: 726 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 727 secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))), 728 }) 729 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 730 "snapd_recovery_mode=factory-reset snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1", 731 "snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1", 732 }) 733 default: 734 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 735 } 736 737 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 738 739 return nil 740 }) 741 defer restore() 742 743 switch { 744 case standalone: 745 err = boot.MakeRunnableStandaloneSystem(model, bootWith, obs) 746 case factoryReset: 747 err = boot.MakeRunnableSystemAfterDataReset(model, bootWith, obs) 748 default: 749 err = boot.MakeRunnableSystem(model, bootWith, obs) 750 } 751 c.Assert(err, IsNil) 752 753 // also do the logical thing and make the next boot go to run mode 754 err = boot.EnsureNextBootToRunMode("20191216") 755 c.Assert(err, IsNil) 756 757 // ensure grub.cfg in boot was installed from internal assets 758 c.Check(mockBootGrubCfg, testutil.FileEquals, string(grubCfgAsset)) 759 760 var installHostWritableDir string 761 if classic { 762 installHostWritableDir = filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data") 763 } else { 764 installHostWritableDir = filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data") 765 } 766 767 // ensure base/gadget/kernel got copied to /var/lib/snapd/snaps 768 core20Snap := filepath.Join(dirs.SnapBlobDirUnder(installHostWritableDir), "core20_3.snap") 769 gadgetSnap := filepath.Join(dirs.SnapBlobDirUnder(installHostWritableDir), "pc_4.snap") 770 pcKernelSnap := filepath.Join(dirs.SnapBlobDirUnder(installHostWritableDir), "pc-kernel_5.snap") 771 c.Check(core20Snap, testutil.FilePresent) 772 c.Check(gadgetSnap, testutil.FilePresent) 773 c.Check(pcKernelSnap, testutil.FilePresent) 774 c.Check(osutil.IsSymlink(core20Snap), Equals, false) 775 c.Check(osutil.IsSymlink(pcKernelSnap), Equals, false) 776 777 // ensure the bootvars got updated the right way 778 mockSeedGrubenv := filepath.Join(mockSeedGrubDir, "grubenv") 779 c.Assert(mockSeedGrubenv, testutil.FilePresent) 780 c.Check(mockSeedGrubenv, testutil.FileContains, "snapd_recovery_mode=run") 781 c.Check(mockSeedGrubenv, testutil.FileContains, "snapd_good_recovery_systems=20191216") 782 mockBootGrubenv := filepath.Join(mockBootGrubDir, "grubenv") 783 c.Check(mockBootGrubenv, testutil.FilePresent) 784 785 // ensure that kernel_status is empty, we specifically want this to be set 786 // to the empty string 787 // use (?m) to match multi-line file in the regex here, because the file is 788 // a grubenv with padding #### blocks 789 c.Check(mockBootGrubenv, testutil.FileMatches, `(?m)^kernel_status=$`) 790 791 // check that we have the extracted kernel in the right places, both in the 792 // old uc16/uc18 location and the new ubuntu-boot partition grub dir 793 extractedKernel := filepath.Join(mockBootGrubDir, "pc-kernel_5.snap", "kernel.efi") 794 c.Check(extractedKernel, testutil.FilePresent) 795 796 // the new uc20 location 797 extractedKernelSymlink := filepath.Join(mockBootGrubDir, "kernel.efi") 798 c.Check(extractedKernelSymlink, testutil.FilePresent) 799 800 // ensure modeenv looks correct 801 var ubuntuDataModeEnvPath, classicLine string 802 if classic { 803 ubuntuDataModeEnvPath = filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/var/lib/snapd/modeenv") 804 classicLine = "\nclassic=true" 805 } else { 806 ubuntuDataModeEnvPath = filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv") 807 } 808 expectedModeenv := fmt.Sprintf(`mode=run 809 recovery_system=20191216 810 current_recovery_systems=20191216 811 good_recovery_systems=20191216 812 base=core20_3.snap 813 gadget=pc_4.snap 814 current_kernels=pc-kernel_5.snap 815 model=my-brand/my-model-uc20%s 816 grade=dangerous 817 model_sign_key_id=Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 818 current_trusted_boot_assets={"grubx64.efi":["5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"]} 819 current_trusted_recovery_boot_assets={"bootx64.efi":["39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"],"grubx64.efi":["aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"]} 820 current_kernel_command_lines=["snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1"] 821 `, classicLine) 822 c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, expectedModeenv) 823 copiedGrubBin := filepath.Join( 824 dirs.SnapBootAssetsDirUnder(installHostWritableDir), 825 "grub", 826 "grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d", 827 ) 828 copiedRecoveryGrubBin := filepath.Join( 829 dirs.SnapBootAssetsDirUnder(installHostWritableDir), 830 "grub", 831 "grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5", 832 ) 833 copiedRecoveryShimBin := filepath.Join( 834 dirs.SnapBootAssetsDirUnder(installHostWritableDir), 835 "grub", 836 "bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37", 837 ) 838 839 // only one file in the cache under new root 840 checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDirUnder(installHostWritableDir), "grub", "*"), []string{ 841 copiedRecoveryShimBin, 842 copiedGrubBin, 843 copiedRecoveryGrubBin, 844 }) 845 // with the right content 846 c.Check(copiedGrubBin, testutil.FileEquals, "grub content") 847 c.Check(copiedRecoveryGrubBin, testutil.FileEquals, "recovery grub content") 848 c.Check(copiedRecoveryShimBin, testutil.FileEquals, "recovery shim content") 849 850 // we checked for fde-setup-hook 851 c.Check(hasFDESetupHookCalled, Equals, true) 852 // make sure TPM was provisioned 853 c.Check(provisionCalls, Equals, 1) 854 // make sure SealKey was called for the run object and the fallback object 855 c.Check(sealKeysCalls, Equals, 2) 856 // PCR handle checks 857 if factoryReset { 858 c.Check(pcrHandleOfKeyCalls, Equals, 1) 859 c.Check(releasePCRHandleCalls, Equals, 1) 860 } else { 861 c.Check(pcrHandleOfKeyCalls, Equals, 0) 862 c.Check(releasePCRHandleCalls, Equals, 0) 863 } 864 865 // make sure the marker file for sealed key was created 866 c.Check(filepath.Join(installHostWritableDir, "/var/lib/snapd/device/fde/sealed-keys"), testutil.FilePresent) 867 868 // make sure we wrote the boot chains data file 869 c.Check(filepath.Join(installHostWritableDir, "/var/lib/snapd/device/fde/boot-chains"), testutil.FilePresent) 870 } 871 872 func (s *makeBootable20Suite) TestMakeSystemRunnable20Install(c *C) { 873 const standalone = false 874 const factoryReset = false 875 const classic = false 876 s.testMakeSystemRunnable20(c, standalone, factoryReset, classic) 877 } 878 879 func (s *makeBootable20Suite) TestMakeSystemRunnable20InstallOnClassic(c *C) { 880 const standalone = false 881 const factoryReset = false 882 const classic = true 883 s.testMakeSystemRunnable20(c, standalone, factoryReset, classic) 884 } 885 886 func (s *makeBootable20Suite) TestMakeSystemRunnable20FactoryReset(c *C) { 887 const standalone = false 888 const factoryReset = true 889 const classic = false 890 s.testMakeSystemRunnable20(c, standalone, factoryReset, classic) 891 } 892 893 func (s *makeBootable20Suite) TestMakeSystemRunnable20FactoryResetOnClassic(c *C) { 894 const standalone = false 895 const factoryReset = true 896 const classic = true 897 s.testMakeSystemRunnable20(c, standalone, factoryReset, classic) 898 } 899 900 func (s *makeBootable20Suite) TestMakeRunnableSystem20ModeInstallBootConfigErr(c *C) { 901 bootloader.Force(nil) 902 903 model := boottest.MakeMockUC20Model() 904 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 905 err := os.MkdirAll(seedSnapsDirs, 0755) 906 c.Assert(err, IsNil) 907 908 // grub on ubuntu-seed 909 mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu") 910 err = os.MkdirAll(mockSeedGrubDir, 0755) 911 c.Assert(err, IsNil) 912 // no recovery grub.cfg so that test fails if it ever reaches that point 913 914 // grub on ubuntu-boot 915 mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu") 916 mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg") 917 err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755) 918 c.Assert(err, IsNil) 919 err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644) 920 c.Assert(err, IsNil) 921 922 unpackedGadgetDir := c.MkDir() 923 924 // make the snaps symlinks so that we can ensure that makebootable follows 925 // the symlinks and copies the files and not the symlinks 926 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 927 type: base 928 version: 5.0 929 `, snap.R(3)) 930 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 931 err = os.Symlink(baseFn, baseInSeed) 932 c.Assert(err, IsNil) 933 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 934 type: kernel 935 version: 5.0 936 `, snap.R(5), 937 [][]string{ 938 {"kernel.efi", "I'm a kernel.efi"}, 939 }, 940 ) 941 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 942 err = os.Symlink(kernelFn, kernelInSeed) 943 c.Assert(err, IsNil) 944 gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc 945 type: gadget 946 version: 5.0 947 `, snap.R(4)) 948 gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename()) 949 err = os.Symlink(gadgetFn, gadgetInSeed) 950 c.Assert(err, IsNil) 951 952 bootWith := &boot.BootableSet{ 953 RecoverySystemLabel: "20191216", 954 BasePath: baseInSeed, 955 Base: baseInfo, 956 KernelPath: kernelInSeed, 957 Kernel: kernelInfo, 958 Gadget: gadgetInfo, 959 GadgetPath: gadgetInSeed, 960 Recovery: false, 961 UnpackedGadgetDir: unpackedGadgetDir, 962 } 963 964 // no grub marker in gadget directory raises an error 965 err = boot.MakeRunnableSystem(model, bootWith, nil) 966 c.Assert(err, ErrorMatches, "internal error: cannot identify run system bootloader: cannot determine bootloader") 967 968 // set up grub.cfg in gadget 969 grubCfg := []byte("#grub cfg") 970 err = ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644) 971 c.Assert(err, IsNil) 972 973 // no write access to destination directory 974 restore := assets.MockInternal("grub.cfg", nil) 975 defer restore() 976 err = boot.MakeRunnableSystem(model, bootWith, nil) 977 c.Assert(err, ErrorMatches, `cannot install managed bootloader assets: internal error: no boot asset for "grub.cfg"`) 978 } 979 980 func (s *makeBootable20Suite) TestMakeRunnableSystem20RunModeSealKeyErr(c *C) { 981 bootloader.Force(nil) 982 983 model := boottest.MakeMockUC20Model() 984 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 985 err := os.MkdirAll(seedSnapsDirs, 0755) 986 c.Assert(err, IsNil) 987 988 // grub on ubuntu-seed 989 mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu") 990 mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg") 991 err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755) 992 c.Assert(err, IsNil) 993 err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644) 994 c.Assert(err, IsNil) 995 996 // setup recovery boot assets 997 err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755) 998 c.Assert(err, IsNil) 999 // SHA3-384: 39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37 1000 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"), 1001 []byte("recovery shim content"), 0644) 1002 c.Assert(err, IsNil) 1003 // SHA3-384: aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5 1004 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"), 1005 []byte("recovery grub content"), 0644) 1006 c.Assert(err, IsNil) 1007 1008 // grub on ubuntu-boot 1009 mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu") 1010 mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg") 1011 err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755) 1012 c.Assert(err, IsNil) 1013 err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644) 1014 c.Assert(err, IsNil) 1015 1016 unpackedGadgetDir := c.MkDir() 1017 grubRecoveryCfg := []byte("#grub-recovery cfg") 1018 grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets") 1019 grubCfg := []byte("#grub cfg") 1020 grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets") 1021 snaptest.PopulateDir(unpackedGadgetDir, [][]string{ 1022 {"grub-recovery.conf", string(grubRecoveryCfg)}, 1023 {"grub.conf", string(grubCfg)}, 1024 {"bootx64.efi", "shim content"}, 1025 {"grubx64.efi", "grub content"}, 1026 {"meta/snap.yaml", gadgetSnapYaml}, 1027 }) 1028 restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset) 1029 defer restore() 1030 restore = assets.MockInternal("grub.cfg", grubCfgAsset) 1031 defer restore() 1032 1033 // make the snaps symlinks so that we can ensure that makebootable follows 1034 // the symlinks and copies the files and not the symlinks 1035 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 1036 type: base 1037 version: 5.0 1038 `, snap.R(3)) 1039 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 1040 err = os.Symlink(baseFn, baseInSeed) 1041 c.Assert(err, IsNil) 1042 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 1043 type: kernel 1044 version: 5.0 1045 `, snap.R(5), 1046 [][]string{ 1047 {"kernel.efi", "I'm a kernel.efi"}, 1048 }, 1049 ) 1050 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 1051 err = os.Symlink(kernelFn, kernelInSeed) 1052 c.Assert(err, IsNil) 1053 gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc 1054 type: gadget 1055 version: 5.0 1056 `, snap.R(4)) 1057 gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename()) 1058 err = os.Symlink(gadgetFn, gadgetInSeed) 1059 c.Assert(err, IsNil) 1060 1061 bootWith := &boot.BootableSet{ 1062 RecoverySystemLabel: "20191216", 1063 BasePath: baseInSeed, 1064 Base: baseInfo, 1065 KernelPath: kernelInSeed, 1066 Kernel: kernelInfo, 1067 Gadget: gadgetInfo, 1068 GadgetPath: gadgetInSeed, 1069 Recovery: false, 1070 UnpackedGadgetDir: unpackedGadgetDir, 1071 } 1072 1073 // set up observer state 1074 useEncryption := true 1075 obs, err := boot.TrustedAssetsInstallObserverForModel(model, unpackedGadgetDir, useEncryption) 1076 c.Assert(obs, NotNil) 1077 c.Assert(err, IsNil) 1078 runBootStruct := &gadget.LaidOutStructure{ 1079 VolumeStructure: &gadget.VolumeStructure{ 1080 Role: gadget.SystemBoot, 1081 }, 1082 } 1083 1084 // only grubx64.efi gets installed to system-boot 1085 _, err = obs.Observe(gadget.ContentWrite, runBootStruct, boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi", 1086 &gadget.ContentChange{After: filepath.Join(unpackedGadgetDir, "grubx64.efi")}) 1087 c.Assert(err, IsNil) 1088 1089 // observe recovery assets 1090 err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir) 1091 c.Assert(err, IsNil) 1092 1093 // set encryption key 1094 myKey := keys.EncryptionKey{} 1095 myKey2 := keys.EncryptionKey{} 1096 for i := range myKey { 1097 myKey[i] = byte(i) 1098 myKey2[i] = byte(128 + i) 1099 } 1100 obs.ChosenEncryptionKeys(myKey, myKey2) 1101 1102 // set a mock recovery kernel 1103 readSystemEssentialCalls := 0 1104 restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 1105 readSystemEssentialCalls++ 1106 return model, []*seed.Snap{mockKernelSeedSnap(snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil 1107 }) 1108 defer restore() 1109 1110 provisionCalls := 0 1111 restore = boot.MockSecbootProvisionTPM(func(mode secboot.TPMProvisionMode, lockoutAuthFile string) error { 1112 provisionCalls++ 1113 c.Check(lockoutAuthFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-lockout-auth")) 1114 c.Check(mode, Equals, secboot.TPMProvisionFull) 1115 return nil 1116 }) 1117 defer restore() 1118 // set mock key sealing 1119 sealKeysCalls := 0 1120 restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error { 1121 sealKeysCalls++ 1122 switch sealKeysCalls { 1123 case 1: 1124 c.Check(keys, HasLen, 1) 1125 c.Check(keys[0].Key, DeepEquals, myKey) 1126 case 2: 1127 c.Check(keys, HasLen, 2) 1128 c.Check(keys[0].Key, DeepEquals, myKey) 1129 c.Check(keys[1].Key, DeepEquals, myKey2) 1130 default: 1131 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 1132 } 1133 c.Assert(params.ModelParams, HasLen, 1) 1134 1135 shim := bootloader.NewBootFile("", filepath.Join(s.rootdir, 1136 "var/lib/snapd/boot-assets/grub/bootx64.efi-39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"), 1137 bootloader.RoleRecovery) 1138 grub := bootloader.NewBootFile("", filepath.Join(s.rootdir, 1139 "var/lib/snapd/boot-assets/grub/grubx64.efi-aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"), 1140 bootloader.RoleRecovery) 1141 runGrub := bootloader.NewBootFile("", filepath.Join(s.rootdir, 1142 "var/lib/snapd/boot-assets/grub/grubx64.efi-5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"), 1143 bootloader.RoleRunMode) 1144 kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 1145 runKernel := bootloader.NewBootFile(filepath.Join(s.rootdir, "var/lib/snapd/snaps/pc-kernel_5.snap"), "kernel.efi", bootloader.RoleRunMode) 1146 1147 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 1148 secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(kernel))), 1149 secboot.NewLoadChain(shim, secboot.NewLoadChain(grub, secboot.NewLoadChain(runGrub, secboot.NewLoadChain(runKernel)))), 1150 }) 1151 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 1152 "snapd_recovery_mode=factory-reset snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1", 1153 "snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1", 1154 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 1155 }) 1156 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 1157 1158 return fmt.Errorf("seal error") 1159 }) 1160 defer restore() 1161 1162 err = boot.MakeRunnableSystem(model, bootWith, obs) 1163 c.Assert(err, ErrorMatches, "cannot seal the encryption keys: seal error") 1164 // the TPM was provisioned 1165 c.Check(provisionCalls, Equals, 1) 1166 } 1167 1168 func (s *makeBootable20Suite) testMakeSystemRunnable20WithCustomKernelArgs(c *C, whichFile, content, errMsg string, cmdlines map[string]string) { 1169 if cmdlines == nil { 1170 cmdlines = map[string]string{} 1171 } 1172 bootloader.Force(nil) 1173 1174 model := boottest.MakeMockUC20Model() 1175 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 1176 err := os.MkdirAll(seedSnapsDirs, 0755) 1177 c.Assert(err, IsNil) 1178 1179 // grub on ubuntu-seed 1180 mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu") 1181 mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg") 1182 err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755) 1183 c.Assert(err, IsNil) 1184 err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644) 1185 c.Assert(err, IsNil) 1186 genv := grubenv.NewEnv(filepath.Join(mockSeedGrubDir, "grubenv")) 1187 c.Assert(genv.Save(), IsNil) 1188 1189 // setup recovery boot assets 1190 err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755) 1191 c.Assert(err, IsNil) 1192 // SHA3-384: 39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37 1193 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"), 1194 []byte("recovery shim content"), 0644) 1195 c.Assert(err, IsNil) 1196 // SHA3-384: aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5 1197 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"), 1198 []byte("recovery grub content"), 0644) 1199 c.Assert(err, IsNil) 1200 1201 // grub on ubuntu-boot 1202 mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu") 1203 mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg") 1204 err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755) 1205 c.Assert(err, IsNil) 1206 err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644) 1207 c.Assert(err, IsNil) 1208 1209 unpackedGadgetDir := c.MkDir() 1210 grubRecoveryCfg := []byte("#grub-recovery cfg") 1211 grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets") 1212 grubCfg := []byte("#grub cfg") 1213 grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets") 1214 gadgetFiles := [][]string{ 1215 {"grub-recovery.conf", string(grubRecoveryCfg)}, 1216 {"grub.conf", string(grubCfg)}, 1217 {"bootx64.efi", "shim content"}, 1218 {"grubx64.efi", "grub content"}, 1219 {"meta/snap.yaml", gadgetSnapYaml}, 1220 {whichFile, content}, 1221 } 1222 snaptest.PopulateDir(unpackedGadgetDir, gadgetFiles) 1223 restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset) 1224 defer restore() 1225 restore = assets.MockInternal("grub.cfg", grubCfgAsset) 1226 defer restore() 1227 1228 // make the snaps symlinks so that we can ensure that makebootable follows 1229 // the symlinks and copies the files and not the symlinks 1230 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 1231 type: base 1232 version: 5.0 1233 `, snap.R(3)) 1234 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 1235 err = os.Symlink(baseFn, baseInSeed) 1236 c.Assert(err, IsNil) 1237 gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc 1238 type: gadget 1239 version: 5.0 1240 `, snap.R(4)) 1241 gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename()) 1242 err = os.Symlink(gadgetFn, gadgetInSeed) 1243 c.Assert(err, IsNil) 1244 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 1245 type: kernel 1246 version: 5.0 1247 `, snap.R(5), 1248 [][]string{ 1249 {"kernel.efi", "I'm a kernel.efi"}, 1250 }, 1251 ) 1252 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 1253 err = os.Symlink(kernelFn, kernelInSeed) 1254 c.Assert(err, IsNil) 1255 1256 bootWith := &boot.BootableSet{ 1257 RecoverySystemLabel: "20191216", 1258 BasePath: baseInSeed, 1259 Base: baseInfo, 1260 Gadget: gadgetInfo, 1261 GadgetPath: gadgetInSeed, 1262 KernelPath: kernelInSeed, 1263 Kernel: kernelInfo, 1264 Recovery: false, 1265 UnpackedGadgetDir: unpackedGadgetDir, 1266 } 1267 1268 // set up observer state 1269 useEncryption := true 1270 obs, err := boot.TrustedAssetsInstallObserverForModel(model, unpackedGadgetDir, useEncryption) 1271 c.Assert(obs, NotNil) 1272 c.Assert(err, IsNil) 1273 runBootStruct := &gadget.LaidOutStructure{ 1274 VolumeStructure: &gadget.VolumeStructure{ 1275 Role: gadget.SystemBoot, 1276 }, 1277 } 1278 1279 // only grubx64.efi gets installed to system-boot 1280 _, err = obs.Observe(gadget.ContentWrite, runBootStruct, boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi", 1281 &gadget.ContentChange{After: filepath.Join(unpackedGadgetDir, "grubx64.efi")}) 1282 c.Assert(err, IsNil) 1283 1284 // observe recovery assets 1285 err = obs.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir) 1286 c.Assert(err, IsNil) 1287 1288 // set a mock recovery kernel 1289 readSystemEssentialCalls := 0 1290 restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 1291 readSystemEssentialCalls++ 1292 return model, []*seed.Snap{mockKernelSeedSnap(snap.R(1)), mockGadgetSeedSnap(c, gadgetFiles)}, nil 1293 }) 1294 defer restore() 1295 1296 provisionCalls := 0 1297 restore = boot.MockSecbootProvisionTPM(func(mode secboot.TPMProvisionMode, lockoutAuthFile string) error { 1298 provisionCalls++ 1299 c.Check(lockoutAuthFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-lockout-auth")) 1300 c.Check(mode, Equals, secboot.TPMProvisionFull) 1301 return nil 1302 }) 1303 defer restore() 1304 // set mock key sealing 1305 sealKeysCalls := 0 1306 restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error { 1307 sealKeysCalls++ 1308 switch sealKeysCalls { 1309 case 1, 2: 1310 // expecting only 2 calls 1311 default: 1312 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 1313 } 1314 c.Assert(params.ModelParams, HasLen, 1) 1315 1316 switch sealKeysCalls { 1317 case 1: 1318 c.Assert(params.ModelParams[0].KernelCmdlines, HasLen, 3) 1319 c.Assert(params.ModelParams[0].KernelCmdlines, testutil.Contains, cmdlines["recover"]) 1320 c.Assert(params.ModelParams[0].KernelCmdlines, testutil.Contains, cmdlines["factory-reset"]) 1321 c.Assert(params.ModelParams[0].KernelCmdlines, testutil.Contains, cmdlines["run"]) 1322 case 2: 1323 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{cmdlines["factory-reset"], cmdlines["recover"]}) 1324 default: 1325 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 1326 } 1327 1328 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 1329 1330 return nil 1331 }) 1332 defer restore() 1333 1334 err = boot.MakeRunnableSystem(model, bootWith, obs) 1335 if errMsg != "" { 1336 c.Assert(err, ErrorMatches, errMsg) 1337 return 1338 } 1339 c.Assert(err, IsNil) 1340 1341 // also do the logical thing and make the next boot go to run mode 1342 err = boot.EnsureNextBootToRunMode("20191216") 1343 c.Assert(err, IsNil) 1344 1345 // ensure grub.cfg in boot was installed from internal assets 1346 c.Check(mockBootGrubCfg, testutil.FileEquals, string(grubCfgAsset)) 1347 1348 // ensure the bootvars got updated the right way 1349 mockSeedGrubenv := filepath.Join(mockSeedGrubDir, "grubenv") 1350 c.Assert(mockSeedGrubenv, testutil.FilePresent) 1351 c.Check(mockSeedGrubenv, testutil.FileContains, "snapd_recovery_mode=run") 1352 c.Check(mockSeedGrubenv, testutil.FileContains, "snapd_good_recovery_systems=20191216") 1353 mockBootGrubenv := filepath.Join(mockBootGrubDir, "grubenv") 1354 c.Check(mockBootGrubenv, testutil.FilePresent) 1355 systemGenv := grubenv.NewEnv(mockBootGrubenv) 1356 c.Assert(systemGenv.Load(), IsNil) 1357 switch whichFile { 1358 case "cmdline.extra": 1359 c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, content) 1360 c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, "") 1361 case "cmdline.full": 1362 c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, "") 1363 c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, content) 1364 } 1365 1366 // ensure modeenv looks correct 1367 ubuntuDataModeEnvPath := filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv") 1368 c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, fmt.Sprintf(`mode=run 1369 recovery_system=20191216 1370 current_recovery_systems=20191216 1371 good_recovery_systems=20191216 1372 base=core20_3.snap 1373 gadget=pc_4.snap 1374 current_kernels=pc-kernel_5.snap 1375 model=my-brand/my-model-uc20 1376 grade=dangerous 1377 model_sign_key_id=Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 1378 current_trusted_boot_assets={"grubx64.efi":["5ee042c15e104b825d6bc15c41cdb026589f1ec57ed966dd3f29f961d4d6924efc54b187743fa3a583b62722882d405d"]} 1379 current_trusted_recovery_boot_assets={"bootx64.efi":["39efae6545f16e39633fbfbef0d5e9fdd45a25d7df8764978ce4d81f255b038046a38d9855e42e5c7c4024e153fd2e37"],"grubx64.efi":["aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5"]} 1380 current_kernel_command_lines=["%v"] 1381 `, cmdlines["run"])) 1382 // make sure the TPM was provisioned 1383 c.Check(provisionCalls, Equals, 1) 1384 // make sure SealKey was called for the run object and the fallback object 1385 c.Check(sealKeysCalls, Equals, 2) 1386 1387 // make sure the marker file for sealed key was created 1388 c.Check(filepath.Join(dirs.SnapFDEDirUnder(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")), "sealed-keys"), testutil.FilePresent) 1389 1390 // make sure we wrote the boot chains data file 1391 c.Check(filepath.Join(dirs.SnapFDEDirUnder(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")), "boot-chains"), testutil.FilePresent) 1392 } 1393 1394 func (s *makeBootable20Suite) TestMakeSystemRunnable20WithCustomKernelExtraArgs(c *C) { 1395 cmdlines := map[string]string{ 1396 "run": "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 foo bar baz", 1397 "recover": "snapd_recovery_mode=recover snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1 foo bar baz", 1398 "factory-reset": "snapd_recovery_mode=factory-reset snapd_recovery_system=20191216 console=ttyS0 console=tty1 panic=-1 foo bar baz", 1399 } 1400 s.testMakeSystemRunnable20WithCustomKernelArgs(c, "cmdline.extra", "foo bar baz", "", cmdlines) 1401 } 1402 1403 func (s *makeBootable20Suite) TestMakeSystemRunnable20WithCustomKernelFullArgs(c *C) { 1404 cmdlines := map[string]string{ 1405 "run": "snapd_recovery_mode=run foo bar baz", 1406 "recover": "snapd_recovery_mode=recover snapd_recovery_system=20191216 foo bar baz", 1407 "factory-reset": "snapd_recovery_mode=factory-reset snapd_recovery_system=20191216 foo bar baz", 1408 } 1409 s.testMakeSystemRunnable20WithCustomKernelArgs(c, "cmdline.full", "foo bar baz", "", cmdlines) 1410 } 1411 1412 func (s *makeBootable20Suite) TestMakeSystemRunnable20WithCustomKernelInvalidArgs(c *C) { 1413 errMsg := `cannot compose the candidate command line: cannot use kernel command line from gadget: invalid kernel command line in cmdline.extra: disallowed kernel argument "snapd=unhappy"` 1414 s.testMakeSystemRunnable20WithCustomKernelArgs(c, "cmdline.extra", "foo bar snapd=unhappy", errMsg, nil) 1415 } 1416 1417 func (s *makeBootable20Suite) TestMakeSystemRunnable20UnhappyMarkRecoveryCapable(c *C) { 1418 bootloader.Force(nil) 1419 1420 model := boottest.MakeMockUC20Model() 1421 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 1422 err := os.MkdirAll(seedSnapsDirs, 0755) 1423 c.Assert(err, IsNil) 1424 1425 // grub on ubuntu-seed 1426 mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu") 1427 mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg") 1428 err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755) 1429 c.Assert(err, IsNil) 1430 err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644) 1431 c.Assert(err, IsNil) 1432 // there is no grubenv in ubuntu-seed so loading from it will fail 1433 1434 // setup recovery boot assets 1435 err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot"), 0755) 1436 c.Assert(err, IsNil) 1437 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/bootx64.efi"), 1438 []byte("recovery shim content"), 0644) 1439 c.Assert(err, IsNil) 1440 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/boot/grubx64.efi"), 1441 []byte("recovery grub content"), 0644) 1442 c.Assert(err, IsNil) 1443 1444 // grub on ubuntu-boot 1445 mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu") 1446 mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg") 1447 err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755) 1448 c.Assert(err, IsNil) 1449 err = ioutil.WriteFile(mockBootGrubCfg, nil, 0644) 1450 c.Assert(err, IsNil) 1451 1452 unpackedGadgetDir := c.MkDir() 1453 grubRecoveryCfg := []byte("#grub-recovery cfg") 1454 grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets") 1455 grubCfg := []byte("#grub cfg") 1456 grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets") 1457 snaptest.PopulateDir(unpackedGadgetDir, [][]string{ 1458 {"grub-recovery.conf", string(grubRecoveryCfg)}, 1459 {"grub.conf", string(grubCfg)}, 1460 {"bootx64.efi", "shim content"}, 1461 {"grubx64.efi", "grub content"}, 1462 {"meta/snap.yaml", gadgetSnapYaml}, 1463 }) 1464 restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset) 1465 defer restore() 1466 restore = assets.MockInternal("grub.cfg", grubCfgAsset) 1467 defer restore() 1468 1469 // make the snaps symlinks so that we can ensure that makebootable follows 1470 // the symlinks and copies the files and not the symlinks 1471 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 1472 type: base 1473 version: 5.0 1474 `, snap.R(3)) 1475 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 1476 err = os.Symlink(baseFn, baseInSeed) 1477 c.Assert(err, IsNil) 1478 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 1479 type: kernel 1480 version: 5.0 1481 `, snap.R(5), 1482 [][]string{ 1483 {"kernel.efi", "I'm a kernel.efi"}, 1484 }, 1485 ) 1486 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 1487 err = os.Symlink(kernelFn, kernelInSeed) 1488 c.Assert(err, IsNil) 1489 gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc 1490 type: gadget 1491 version: 5.0 1492 `, snap.R(4)) 1493 gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename()) 1494 err = os.Symlink(gadgetFn, gadgetInSeed) 1495 c.Assert(err, IsNil) 1496 1497 bootWith := &boot.BootableSet{ 1498 RecoverySystemLabel: "20191216", 1499 BasePath: baseInSeed, 1500 Base: baseInfo, 1501 KernelPath: kernelInSeed, 1502 Kernel: kernelInfo, 1503 Gadget: gadgetInfo, 1504 GadgetPath: gadgetInSeed, 1505 Recovery: false, 1506 UnpackedGadgetDir: unpackedGadgetDir, 1507 } 1508 1509 // set a mock recovery kernel 1510 readSystemEssentialCalls := 0 1511 restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 1512 readSystemEssentialCalls++ 1513 return model, []*seed.Snap{mockKernelSeedSnap(snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil 1514 }) 1515 defer restore() 1516 1517 err = boot.MakeRunnableSystem(model, bootWith, nil) 1518 c.Assert(err, ErrorMatches, `cannot record "20191216" as a recovery capable system: open .*/run/mnt/ubuntu-seed/EFI/ubuntu/grubenv: no such file or directory`) 1519 1520 } 1521 1522 func (s *makeBootable20UbootSuite) TestUbootMakeBootableImage20TraditionalUbootenvFails(c *C) { 1523 bootloader.Force(nil) 1524 model := boottest.MakeMockUC20Model() 1525 1526 unpackedGadgetDir := c.MkDir() 1527 ubootEnv := []byte("#uboot env") 1528 err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), ubootEnv, 0644) 1529 c.Assert(err, IsNil) 1530 1531 // on uc20 the seed layout if different 1532 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 1533 err = os.MkdirAll(seedSnapsDirs, 0755) 1534 c.Assert(err, IsNil) 1535 1536 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 1537 type: base 1538 version: 5.0 1539 `, snap.R(3)) 1540 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 1541 err = os.Rename(baseFn, baseInSeed) 1542 c.Assert(err, IsNil) 1543 kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel 1544 type: kernel 1545 version: 5.0 1546 `, snap.R(5), [][]string{ 1547 {"kernel.img", "I'm a kernel"}, 1548 {"initrd.img", "...and I'm an initrd"}, 1549 {"dtbs/foo.dtb", "foo dtb"}, 1550 {"dtbs/bar.dto", "bar dtbo"}, 1551 }) 1552 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 1553 err = os.Rename(kernelFn, kernelInSeed) 1554 c.Assert(err, IsNil) 1555 1556 label := "20191209" 1557 recoverySystemDir := filepath.Join("/systems", label) 1558 bootWith := &boot.BootableSet{ 1559 Base: baseInfo, 1560 BasePath: baseInSeed, 1561 Kernel: kernelInfo, 1562 KernelPath: kernelInSeed, 1563 RecoverySystemDir: recoverySystemDir, 1564 RecoverySystemLabel: label, 1565 UnpackedGadgetDir: unpackedGadgetDir, 1566 Recovery: true, 1567 } 1568 1569 // TODO:UC20: enable this use case 1570 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 1571 c.Assert(err, ErrorMatches, `cannot install bootloader: non-empty uboot.env not supported on UC20\+ yet`) 1572 } 1573 1574 func (s *makeBootable20UbootSuite) TestUbootMakeBootableImage20BootScr(c *C) { 1575 model := boottest.MakeMockUC20Model() 1576 1577 unpackedGadgetDir := c.MkDir() 1578 // the uboot.conf must be empty for this to work/do the right thing 1579 err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), nil, 0644) 1580 c.Assert(err, IsNil) 1581 1582 // on uc20 the seed layout if different 1583 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 1584 err = os.MkdirAll(seedSnapsDirs, 0755) 1585 c.Assert(err, IsNil) 1586 1587 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 1588 type: base 1589 version: 5.0 1590 `, snap.R(3)) 1591 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 1592 err = os.Rename(baseFn, baseInSeed) 1593 c.Assert(err, IsNil) 1594 kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel 1595 type: kernel 1596 version: 5.0 1597 `, snap.R(5), [][]string{ 1598 {"kernel.img", "I'm a kernel"}, 1599 {"initrd.img", "...and I'm an initrd"}, 1600 {"dtbs/foo.dtb", "foo dtb"}, 1601 {"dtbs/bar.dto", "bar dtbo"}, 1602 }) 1603 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 1604 err = os.Rename(kernelFn, kernelInSeed) 1605 c.Assert(err, IsNil) 1606 1607 label := "20191209" 1608 recoverySystemDir := filepath.Join("/systems", label) 1609 bootWith := &boot.BootableSet{ 1610 Base: baseInfo, 1611 BasePath: baseInSeed, 1612 Kernel: kernelInfo, 1613 KernelPath: kernelInSeed, 1614 RecoverySystemDir: recoverySystemDir, 1615 RecoverySystemLabel: label, 1616 UnpackedGadgetDir: unpackedGadgetDir, 1617 Recovery: true, 1618 } 1619 1620 err = boot.MakeBootableImage(model, s.rootdir, bootWith, nil) 1621 c.Assert(err, IsNil) 1622 1623 // since uboot.conf was absent, we won't have installed the uboot.env, as 1624 // it is expected that the gadget assets would have installed boot.scr 1625 // instead 1626 c.Check(filepath.Join(s.rootdir, "uboot.env"), testutil.FileAbsent) 1627 1628 c.Check(s.bootloader.BootVars, DeepEquals, map[string]string{ 1629 "snapd_recovery_system": label, 1630 "snapd_recovery_mode": "install", 1631 }) 1632 1633 // ensure the correct recovery system configuration was set 1634 c.Check( 1635 s.bootloader.ExtractRecoveryKernelAssetsCalls, 1636 DeepEquals, 1637 []bootloadertest.ExtractedRecoveryKernelCall{{ 1638 RecoverySystemDir: recoverySystemDir, 1639 S: kernelInfo, 1640 }}, 1641 ) 1642 } 1643 1644 func (s *makeBootable20UbootSuite) TestUbootMakeRunnableSystem20RunModeBootSel(c *C) { 1645 bootloader.Force(nil) 1646 1647 model := boottest.MakeMockUC20Model() 1648 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 1649 err := os.MkdirAll(seedSnapsDirs, 0755) 1650 c.Assert(err, IsNil) 1651 1652 // uboot on ubuntu-seed 1653 mockSeedUbootBootSel := filepath.Join(boot.InitramfsUbuntuSeedDir, "uboot/ubuntu/boot.sel") 1654 err = os.MkdirAll(filepath.Dir(mockSeedUbootBootSel), 0755) 1655 c.Assert(err, IsNil) 1656 env, err := ubootenv.Create(mockSeedUbootBootSel, 4096) 1657 c.Assert(err, IsNil) 1658 c.Assert(env.Save(), IsNil) 1659 1660 // uboot on ubuntu-boot (as if it was installed when creating the partition) 1661 mockBootUbootBootSel := filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/boot.sel") 1662 err = os.MkdirAll(filepath.Dir(mockBootUbootBootSel), 0755) 1663 c.Assert(err, IsNil) 1664 env, err = ubootenv.Create(mockBootUbootBootSel, 4096) 1665 c.Assert(err, IsNil) 1666 c.Assert(env.Save(), IsNil) 1667 1668 unpackedGadgetDir := c.MkDir() 1669 c.Assert(ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "uboot.conf"), nil, 0644), IsNil) 1670 1671 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 1672 type: base 1673 version: 5.0 1674 `, snap.R(3)) 1675 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 1676 err = os.Rename(baseFn, baseInSeed) 1677 c.Assert(err, IsNil) 1678 gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc 1679 type: gadget 1680 version: 5.0 1681 `, snap.R(4)) 1682 gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename()) 1683 err = os.Symlink(gadgetFn, gadgetInSeed) 1684 c.Assert(err, IsNil) 1685 kernelSnapFiles := [][]string{ 1686 {"kernel.img", "I'm a kernel"}, 1687 {"initrd.img", "...and I'm an initrd"}, 1688 {"dtbs/foo.dtb", "foo dtb"}, 1689 {"dtbs/bar.dto", "bar dtbo"}, 1690 } 1691 kernelFn, kernelInfo := makeSnapWithFiles(c, "arm-kernel", `name: arm-kernel 1692 type: kernel 1693 version: 5.0 1694 `, snap.R(5), kernelSnapFiles) 1695 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 1696 err = os.Rename(kernelFn, kernelInSeed) 1697 c.Assert(err, IsNil) 1698 1699 bootWith := &boot.BootableSet{ 1700 RecoverySystemLabel: "20191216", 1701 BasePath: baseInSeed, 1702 Base: baseInfo, 1703 Gadget: gadgetInfo, 1704 GadgetPath: gadgetInSeed, 1705 KernelPath: kernelInSeed, 1706 Kernel: kernelInfo, 1707 Recovery: false, 1708 UnpackedGadgetDir: unpackedGadgetDir, 1709 } 1710 err = boot.MakeRunnableSystem(model, bootWith, nil) 1711 c.Assert(err, IsNil) 1712 1713 // also do the logical next thing which is to ensure that the system 1714 // reboots into run mode 1715 err = boot.EnsureNextBootToRunMode("20191216") 1716 c.Assert(err, IsNil) 1717 1718 // ensure base/kernel got copied to /var/lib/snapd/snaps 1719 c.Check(filepath.Join(dirs.SnapBlobDirUnder(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")), "core20_3.snap"), testutil.FilePresent) 1720 c.Check(filepath.Join(dirs.SnapBlobDirUnder(filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")), "arm-kernel_5.snap"), testutil.FilePresent) 1721 1722 // ensure the bootvars on ubuntu-seed got updated the right way 1723 mockSeedUbootenv := filepath.Join(boot.InitramfsUbuntuSeedDir, "uboot/ubuntu/boot.sel") 1724 uenvSeed, err := ubootenv.Open(mockSeedUbootenv) 1725 c.Assert(err, IsNil) 1726 c.Assert(uenvSeed.Get("snapd_recovery_mode"), Equals, "run") 1727 1728 // now check ubuntu-boot boot.sel 1729 mockBootUbootenv := filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/boot.sel") 1730 uenvBoot, err := ubootenv.Open(mockBootUbootenv) 1731 c.Assert(err, IsNil) 1732 c.Assert(uenvBoot.Get("snap_try_kernel"), Equals, "") 1733 c.Assert(uenvBoot.Get("snap_kernel"), Equals, "arm-kernel_5.snap") 1734 c.Assert(uenvBoot.Get("kernel_status"), Equals, boot.DefaultStatus) 1735 1736 // check that we have the extracted kernel in the right places, in the 1737 // old uc16/uc18 location 1738 for _, file := range kernelSnapFiles { 1739 fName := file[0] 1740 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "uboot/ubuntu/arm-kernel_5.snap", fName), testutil.FilePresent) 1741 } 1742 1743 // ensure modeenv looks correct 1744 ubuntuDataModeEnvPath := filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv") 1745 c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, `mode=run 1746 recovery_system=20191216 1747 current_recovery_systems=20191216 1748 good_recovery_systems=20191216 1749 base=core20_3.snap 1750 gadget=pc_4.snap 1751 current_kernels=arm-kernel_5.snap 1752 model=my-brand/my-model-uc20 1753 grade=dangerous 1754 model_sign_key_id=Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 1755 `) 1756 } 1757 1758 func (s *makeBootable20Suite) TestMakeRecoverySystemBootableAtRuntime20(c *C) { 1759 bootloader.Force(nil) 1760 1761 // on uc20 the seed layout if different 1762 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 1763 err := os.MkdirAll(seedSnapsDirs, 0755) 1764 c.Assert(err, IsNil) 1765 1766 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 1767 type: kernel 1768 version: 5.0 1769 `, snap.R(5), [][]string{ 1770 {"kernel.efi", "I'm a kernel.efi"}, 1771 }) 1772 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 1773 err = os.Rename(kernelFn, kernelInSeed) 1774 c.Assert(err, IsNil) 1775 1776 gadgets := map[string]string{} 1777 for _, rev := range []snap.Revision{snap.R(1), snap.R(5)} { 1778 gadgetFn, gadgetInfo := makeSnapWithFiles(c, "pc", gadgetSnapYaml, rev, [][]string{ 1779 {"grub.conf", ""}, 1780 {"meta/snap.yaml", gadgetSnapYaml}, 1781 {"cmdline.full", fmt.Sprintf("args from gadget rev %s", rev.String())}, 1782 }) 1783 gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename()) 1784 err = os.Rename(gadgetFn, gadgetInSeed) 1785 c.Assert(err, IsNil) 1786 // keep track of the gadgets 1787 gadgets[rev.String()] = gadgetInSeed 1788 } 1789 1790 snaptest.PopulateDir(s.rootdir, [][]string{ 1791 {"EFI/ubuntu/grub.cfg", "this is grub"}, 1792 {"EFI/ubuntu/grubenv", "canary"}, 1793 }) 1794 1795 label := "20191209" 1796 recoverySystemDir := filepath.Join("/systems", label) 1797 err = boot.MakeRecoverySystemBootable(s.rootdir, recoverySystemDir, &boot.RecoverySystemBootableSet{ 1798 Kernel: kernelInfo, 1799 KernelPath: kernelInSeed, 1800 // use gadget revision 1 1801 GadgetSnapOrDir: gadgets["1"], 1802 // like it's called when creating a new recovery system 1803 PrepareImageTime: false, 1804 }) 1805 c.Assert(err, IsNil) 1806 // the recovery partition grubenv was not modified 1807 c.Check(filepath.Join(s.rootdir, "EFI/ubuntu/grubenv"), testutil.FileEquals, "canary") 1808 1809 systemGenv := grubenv.NewEnv(filepath.Join(s.rootdir, recoverySystemDir, "grubenv")) 1810 c.Assert(systemGenv.Load(), IsNil) 1811 c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap") 1812 c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, "") 1813 c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, "args from gadget rev 1") 1814 1815 // create another system under a new label 1816 newLabel := "20210420" 1817 newRecoverySystemDir := filepath.Join("/systems", newLabel) 1818 // with a different gadget revision, but same kernel 1819 err = boot.MakeRecoverySystemBootable(s.rootdir, newRecoverySystemDir, &boot.RecoverySystemBootableSet{ 1820 Kernel: kernelInfo, 1821 KernelPath: kernelInSeed, 1822 GadgetSnapOrDir: gadgets["5"], 1823 // like it's called when creating a new recovery system 1824 PrepareImageTime: false, 1825 }) 1826 c.Assert(err, IsNil) 1827 1828 systemGenv = grubenv.NewEnv(filepath.Join(s.rootdir, newRecoverySystemDir, "grubenv")) 1829 c.Assert(systemGenv.Load(), IsNil) 1830 c.Check(systemGenv.Get("snapd_recovery_kernel"), Equals, "/snaps/pc-kernel_5.snap") 1831 c.Check(systemGenv.Get("snapd_extra_cmdline_args"), Equals, "") 1832 c.Check(systemGenv.Get("snapd_full_cmdline_args"), Equals, "args from gadget rev 5") 1833 } 1834 1835 func (s *makeBootable20Suite) TestMakeBootablePartition(c *C) { 1836 bootloader.Force(nil) 1837 1838 unpackedGadgetDir := c.MkDir() 1839 grubRecoveryCfg := "#grub-recovery cfg" 1840 grubRecoveryCfgAsset := "#grub-recovery cfg from assets" 1841 grubCfg := "#grub cfg" 1842 snaptest.PopulateDir(unpackedGadgetDir, [][]string{ 1843 {"grub-recovery.conf", grubRecoveryCfg}, 1844 {"grub.conf", grubCfg}, 1845 {"meta/snap.yaml", gadgetSnapYaml}, 1846 }) 1847 restore := assets.MockInternal("grub-recovery.cfg", []byte(grubRecoveryCfgAsset)) 1848 defer restore() 1849 1850 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 1851 err := os.MkdirAll(seedSnapsDirs, 0755) 1852 c.Assert(err, IsNil) 1853 1854 gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc 1855 type: gadget 1856 version: 5.0 1857 `, snap.R(4)) 1858 gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename()) 1859 err = os.Symlink(gadgetFn, gadgetInSeed) 1860 c.Assert(err, IsNil) 1861 1862 baseFn, baseInfo := makeSnap(c, "core22", `name: core22 1863 type: base 1864 version: 5.0 1865 `, snap.R(3)) 1866 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 1867 err = os.Rename(baseFn, baseInSeed) 1868 c.Assert(err, IsNil) 1869 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 1870 type: kernel 1871 version: 5.0 1872 `, snap.R(5), [][]string{ 1873 {"kernel.efi", "I'm a kernel.efi"}, 1874 }) 1875 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 1876 err = os.Rename(kernelFn, kernelInSeed) 1877 c.Assert(err, IsNil) 1878 1879 bootWith := &boot.BootableSet{ 1880 Base: baseInfo, 1881 BasePath: baseInSeed, 1882 Kernel: kernelInfo, 1883 KernelPath: kernelInSeed, 1884 Gadget: gadgetInfo, 1885 GadgetPath: gadgetInSeed, 1886 RecoverySystemLabel: "", 1887 UnpackedGadgetDir: unpackedGadgetDir, 1888 Recovery: false, 1889 } 1890 1891 opts := &bootloader.Options{ 1892 PrepareImageTime: false, 1893 // We need the same configuration that a recovery partition, 1894 // as we will chainload to grub in the boot partition. 1895 Role: bootloader.RoleRecovery, 1896 } 1897 partMntDir := filepath.Join(s.rootdir, "/partition") 1898 err = os.MkdirAll(partMntDir, 0755) 1899 c.Assert(err, IsNil) 1900 err = boot.MakeBootablePartition(partMntDir, opts, bootWith, boot.ModeRun, []string{}) 1901 c.Assert(err, IsNil) 1902 1903 // ensure we have only grub.cfg and grubenv 1904 files, err := filepath.Glob(filepath.Join(partMntDir, "EFI/ubuntu/*")) 1905 c.Assert(err, IsNil) 1906 c.Check(files, HasLen, 2) 1907 // and nothing else 1908 files, err = filepath.Glob(filepath.Join(partMntDir, "EFI/*")) 1909 c.Assert(err, IsNil) 1910 c.Check(files, HasLen, 1) 1911 files, err = filepath.Glob(filepath.Join(partMntDir, "*")) 1912 c.Assert(err, IsNil) 1913 c.Check(files, HasLen, 1) 1914 // check that the recovery bootloader configuration was installed with 1915 // the correct content 1916 c.Check(filepath.Join(partMntDir, "EFI/ubuntu/grub.cfg"), testutil.FileEquals, grubRecoveryCfgAsset) 1917 1918 // ensure the correct recovery system configuration was set 1919 seedGenv := grubenv.NewEnv(filepath.Join(partMntDir, "EFI/ubuntu/grubenv")) 1920 c.Assert(seedGenv.Load(), IsNil) 1921 c.Check(seedGenv.Get("snapd_recovery_system"), Equals, "") 1922 c.Check(seedGenv.Get("snapd_recovery_mode"), Equals, boot.ModeRun) 1923 c.Check(seedGenv.Get("snapd_good_recovery_systems"), Equals, "") 1924 } 1925 1926 func (s *makeBootable20Suite) TestMakeRunnableSystemNoGoodRecoverySystems(c *C) { 1927 bootloader.Force(nil) 1928 model := boottest.MakeMockUC20Model() 1929 seedSnapsDirs := filepath.Join(s.rootdir, "/snaps") 1930 err := os.MkdirAll(seedSnapsDirs, 0755) 1931 c.Assert(err, IsNil) 1932 1933 // grub on ubuntu-seed 1934 mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu") 1935 mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg") 1936 err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755) 1937 c.Assert(err, IsNil) 1938 err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644) 1939 c.Assert(err, IsNil) 1940 genv := grubenv.NewEnv(filepath.Join(mockSeedGrubDir, "grubenv")) 1941 c.Assert(genv.Save(), IsNil) 1942 1943 // mock grub so it is detected as the current bootloader 1944 unpackedGadgetDir := c.MkDir() 1945 grubRecoveryCfg := []byte("#grub-recovery cfg") 1946 grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets") 1947 grubCfg := []byte("#grub cfg") 1948 grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets") 1949 snaptest.PopulateDir(unpackedGadgetDir, [][]string{ 1950 {"grub-recovery.conf", string(grubRecoveryCfg)}, 1951 {"grub.conf", string(grubCfg)}, 1952 {"bootx64.efi", "shim content"}, 1953 {"grubx64.efi", "grub content"}, 1954 {"meta/snap.yaml", gadgetSnapYaml}, 1955 }) 1956 restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset) 1957 defer restore() 1958 restore = assets.MockInternal("grub.cfg", grubCfgAsset) 1959 defer restore() 1960 1961 // make the snaps symlinks so that we can ensure that makebootable follows 1962 // the symlinks and copies the files and not the symlinks 1963 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 1964 type: base 1965 version: 5.0 1966 `, snap.R(3)) 1967 baseInSeed := filepath.Join(seedSnapsDirs, baseInfo.Filename()) 1968 err = os.Symlink(baseFn, baseInSeed) 1969 c.Assert(err, IsNil) 1970 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 1971 type: kernel 1972 version: 5.0 1973 `, snap.R(5), 1974 [][]string{ 1975 {"kernel.efi", "I'm a kernel.efi"}, 1976 }, 1977 ) 1978 kernelInSeed := filepath.Join(seedSnapsDirs, kernelInfo.Filename()) 1979 err = os.Symlink(kernelFn, kernelInSeed) 1980 c.Assert(err, IsNil) 1981 gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc 1982 type: gadget 1983 version: 5.0 1984 `, snap.R(4)) 1985 gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename()) 1986 err = os.Symlink(gadgetFn, gadgetInSeed) 1987 c.Assert(err, IsNil) 1988 1989 bootWith := &boot.BootableSet{ 1990 BasePath: baseInSeed, 1991 Base: baseInfo, 1992 KernelPath: kernelInSeed, 1993 Kernel: kernelInfo, 1994 Gadget: gadgetInfo, 1995 GadgetPath: gadgetInSeed, 1996 Recovery: false, 1997 UnpackedGadgetDir: unpackedGadgetDir, 1998 } 1999 2000 err = boot.MakeRunnableSystem(model, bootWith, nil) 2001 c.Assert(err, IsNil) 2002 2003 // ensure that there are no good recovery systems as RecoverySystemLabel was empty 2004 mockSeedGrubenv := filepath.Join(mockSeedGrubDir, "grubenv") 2005 c.Check(mockSeedGrubenv, testutil.FilePresent) 2006 systemGenv := grubenv.NewEnv(mockSeedGrubenv) 2007 c.Assert(systemGenv.Load(), IsNil) 2008 c.Check(systemGenv.Get("snapd_good_recovery_systems"), Equals, "") 2009 } 2010 2011 func (s *makeBootable20Suite) TestMakeRunnableSystemStandaloneSnapsCopy(c *C) { 2012 bootloader.Force(nil) 2013 model := boottest.MakeMockUC20Model() 2014 snapsDirs := filepath.Join(s.rootdir, "/somewhere") 2015 err := os.MkdirAll(snapsDirs, 0755) 2016 c.Assert(err, IsNil) 2017 2018 // grub on ubuntu-seed 2019 mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu") 2020 mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg") 2021 err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755) 2022 c.Assert(err, IsNil) 2023 err = ioutil.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644) 2024 c.Assert(err, IsNil) 2025 genv := grubenv.NewEnv(filepath.Join(mockSeedGrubDir, "grubenv")) 2026 c.Assert(genv.Save(), IsNil) 2027 2028 // mock grub so it is detected as the current bootloader 2029 unpackedGadgetDir := c.MkDir() 2030 grubRecoveryCfg := []byte("#grub-recovery cfg") 2031 grubRecoveryCfgAsset := []byte("#grub-recovery cfg from assets") 2032 grubCfg := []byte("#grub cfg") 2033 grubCfgAsset := []byte("# Snapd-Boot-Config-Edition: 1\n#grub cfg from assets") 2034 snaptest.PopulateDir(unpackedGadgetDir, [][]string{ 2035 {"grub-recovery.conf", string(grubRecoveryCfg)}, 2036 {"grub.conf", string(grubCfg)}, 2037 {"bootx64.efi", "shim content"}, 2038 {"grubx64.efi", "grub content"}, 2039 {"meta/snap.yaml", gadgetSnapYaml}, 2040 }) 2041 restore := assets.MockInternal("grub-recovery.cfg", grubRecoveryCfgAsset) 2042 defer restore() 2043 restore = assets.MockInternal("grub.cfg", grubCfgAsset) 2044 defer restore() 2045 2046 // make the snaps symlinks so that we can ensure that makebootable follows 2047 // the symlinks and copies the files and not the symlinks 2048 baseFn, baseInfo := makeSnap(c, "core20", `name: core20 2049 type: base 2050 version: 5.0 2051 `, snap.R(3)) 2052 baseInSeed := filepath.Join(snapsDirs, "core20") 2053 err = os.Symlink(baseFn, baseInSeed) 2054 c.Assert(err, IsNil) 2055 kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel 2056 type: kernel 2057 version: 4.1 2058 `, snap.R(5), 2059 [][]string{ 2060 {"kernel.efi", "I'm a kernel.efi"}, 2061 }, 2062 ) 2063 kernelInSeed := filepath.Join(snapsDirs, "pc-kernel_4.1.snap") 2064 err = os.Symlink(kernelFn, kernelInSeed) 2065 c.Assert(err, IsNil) 2066 gadgetFn, gadgetInfo := makeSnap(c, "pc", `name: pc 2067 type: gadget 2068 version: 3.0 2069 `, snap.R(4)) 2070 gadgetInSeed := filepath.Join(snapsDirs, "pc_3.0.snap") 2071 err = os.Symlink(gadgetFn, gadgetInSeed) 2072 c.Assert(err, IsNil) 2073 2074 bootWith := &boot.BootableSet{ 2075 RecoverySystemLabel: "20221004", 2076 BasePath: baseInSeed, 2077 Base: baseInfo, 2078 KernelPath: kernelInSeed, 2079 Kernel: kernelInfo, 2080 Gadget: gadgetInfo, 2081 GadgetPath: gadgetInSeed, 2082 Recovery: false, 2083 UnpackedGadgetDir: unpackedGadgetDir, 2084 } 2085 2086 err = boot.MakeRunnableSystem(model, bootWith, nil) 2087 c.Assert(err, IsNil) 2088 2089 installHostWritableDir := filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data") 2090 // ensure base/gadget/kernel got copied to /var/lib/snapd/snaps 2091 core20Snap := filepath.Join(dirs.SnapBlobDirUnder(installHostWritableDir), "core20_3.snap") 2092 gadgetSnap := filepath.Join(dirs.SnapBlobDirUnder(installHostWritableDir), "pc_4.snap") 2093 pcKernelSnap := filepath.Join(dirs.SnapBlobDirUnder(installHostWritableDir), "pc-kernel_5.snap") 2094 c.Check(core20Snap, testutil.FilePresent) 2095 c.Check(gadgetSnap, testutil.FilePresent) 2096 c.Check(pcKernelSnap, testutil.FilePresent) 2097 c.Check(osutil.IsSymlink(core20Snap), Equals, false) 2098 c.Check(osutil.IsSymlink(pcKernelSnap), Equals, false) 2099 c.Check(osutil.IsSymlink(gadgetSnap), Equals, false) 2100 2101 // check modeenv 2102 ubuntuDataModeEnvPath := filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/system-data/var/lib/snapd/modeenv") 2103 expectedModeenv := `mode=run 2104 recovery_system=20221004 2105 current_recovery_systems=20221004 2106 good_recovery_systems=20221004 2107 base=core20_3.snap 2108 gadget=pc_4.snap 2109 current_kernels=pc-kernel_5.snap 2110 model=my-brand/my-model-uc20 2111 grade=dangerous 2112 model_sign_key_id=Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 2113 current_kernel_command_lines=["snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1"] 2114 ` 2115 c.Check(ubuntuDataModeEnvPath, testutil.FileEquals, expectedModeenv) 2116 } 2117 2118 func (s *makeBootable20Suite) TestMakeStandaloneSystemRunnable20Install(c *C) { 2119 const standalone = true 2120 const factoryReset = false 2121 const classic = false 2122 s.testMakeSystemRunnable20(c, standalone, factoryReset, classic) 2123 } 2124 2125 func (s *makeBootable20Suite) TestMakeStandaloneSystemRunnable20InstallOnClassic(c *C) { 2126 const standalone = true 2127 const factoryReset = false 2128 const classic = true 2129 s.testMakeSystemRunnable20(c, standalone, factoryReset, classic) 2130 }