mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
We need to resolve the boot chains another place based on the trusted assets we encountered to be installed. At this point it could be any chain. We will need to discover later what the correct chain is. Also make TrustedAssets return an unsorted data structure to make sure we do not use the order like the comments claimed.
5551 lines
175 KiB
Go
5551 lines
175 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
|
|
/*
|
|
* Copyright (C) 2014-2022 Canonical Ltd
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 3 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
package boot_test
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
. "gopkg.in/check.v1"
|
|
|
|
"github.com/snapcore/snapd/asserts"
|
|
"github.com/snapcore/snapd/asserts/assertstest"
|
|
"github.com/snapcore/snapd/boot"
|
|
"github.com/snapcore/snapd/boot/boottest"
|
|
"github.com/snapcore/snapd/bootloader"
|
|
"github.com/snapcore/snapd/bootloader/bootloadertest"
|
|
"github.com/snapcore/snapd/dirs"
|
|
"github.com/snapcore/snapd/osutil/kcmdline"
|
|
"github.com/snapcore/snapd/release"
|
|
"github.com/snapcore/snapd/secboot"
|
|
"github.com/snapcore/snapd/seed"
|
|
"github.com/snapcore/snapd/snap"
|
|
"github.com/snapcore/snapd/snap/snaptest"
|
|
"github.com/snapcore/snapd/strutil"
|
|
"github.com/snapcore/snapd/testutil"
|
|
"github.com/snapcore/snapd/timings"
|
|
)
|
|
|
|
func TestBoot(t *testing.T) { TestingT(t) }
|
|
|
|
type baseBootenvSuite struct {
|
|
testutil.BaseTest
|
|
|
|
rootdir string
|
|
bootdir string
|
|
cmdlineFile string
|
|
}
|
|
|
|
func (s *baseBootenvSuite) SetUpTest(c *C) {
|
|
s.BaseTest.SetUpTest(c)
|
|
|
|
restore := release.MockOnClassic(false)
|
|
s.AddCleanup(restore)
|
|
|
|
s.rootdir = c.MkDir()
|
|
dirs.SetRootDir(s.rootdir)
|
|
s.AddCleanup(func() { dirs.SetRootDir("") })
|
|
restore = snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})
|
|
s.AddCleanup(restore)
|
|
|
|
s.bootdir = filepath.Join(s.rootdir, "boot")
|
|
|
|
s.cmdlineFile = filepath.Join(c.MkDir(), "cmdline")
|
|
restore = kcmdline.MockProcCmdline(s.cmdlineFile)
|
|
s.AddCleanup(restore)
|
|
}
|
|
|
|
func (s *baseBootenvSuite) forceBootloader(bloader bootloader.Bootloader) {
|
|
bootloader.Force(bloader)
|
|
s.AddCleanup(func() { bootloader.Force(nil) })
|
|
}
|
|
|
|
func (s *baseBootenvSuite) stampSealedKeys(c *C, rootdir string) {
|
|
stamp := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "sealed-keys")
|
|
c.Assert(os.MkdirAll(filepath.Dir(stamp), 0755), IsNil)
|
|
err := os.WriteFile(stamp, nil, 0644)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (s *baseBootenvSuite) mockCmdline(c *C, cmdline string) {
|
|
c.Assert(os.WriteFile(s.cmdlineFile, []byte(cmdline), 0644), IsNil)
|
|
}
|
|
|
|
// mockAssetsCache mocks the listed assets in the boot assets cache by creating
|
|
// an empty file for each.
|
|
func mockAssetsCache(c *C, rootdir, bootloaderName string, cachedAssets []string) {
|
|
p := filepath.Join(dirs.SnapBootAssetsDirUnder(rootdir), bootloaderName)
|
|
err := os.MkdirAll(p, 0755)
|
|
c.Assert(err, IsNil)
|
|
for _, cachedAsset := range cachedAssets {
|
|
err = os.WriteFile(filepath.Join(p, cachedAsset), nil, 0644)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
}
|
|
|
|
type bootenvSuite struct {
|
|
baseBootenvSuite
|
|
|
|
bootloader *bootloadertest.MockBootloader
|
|
}
|
|
|
|
var _ = Suite(&bootenvSuite{})
|
|
|
|
func (s *bootenvSuite) SetUpTest(c *C) {
|
|
s.baseBootenvSuite.SetUpTest(c)
|
|
|
|
s.bootloader = bootloadertest.Mock("mock", c.MkDir())
|
|
s.forceBootloader(s.bootloader)
|
|
}
|
|
|
|
type baseBootenv20Suite struct {
|
|
baseBootenvSuite
|
|
|
|
kern1 snap.PlaceInfo
|
|
kern2 snap.PlaceInfo
|
|
ukern1 snap.PlaceInfo
|
|
ukern2 snap.PlaceInfo
|
|
base1 snap.PlaceInfo
|
|
base2 snap.PlaceInfo
|
|
gadget1 snap.PlaceInfo
|
|
gadget2 snap.PlaceInfo
|
|
|
|
normalDefaultState *bootenv20Setup
|
|
normalTryingKernelState *bootenv20Setup
|
|
}
|
|
|
|
func (s *baseBootenv20Suite) SetUpTest(c *C) {
|
|
s.baseBootenvSuite.SetUpTest(c)
|
|
|
|
var err error
|
|
s.kern1, err = snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
|
|
c.Assert(err, IsNil)
|
|
s.kern2, err = snap.ParsePlaceInfoFromSnapFileName("pc-kernel_2.snap")
|
|
c.Assert(err, IsNil)
|
|
|
|
s.ukern1, err = snap.ParsePlaceInfoFromSnapFileName("pc-kernel_x1.snap")
|
|
c.Assert(err, IsNil)
|
|
s.ukern2, err = snap.ParsePlaceInfoFromSnapFileName("pc-kernel_x2.snap")
|
|
c.Assert(err, IsNil)
|
|
|
|
s.base1, err = snap.ParsePlaceInfoFromSnapFileName("core20_1.snap")
|
|
c.Assert(err, IsNil)
|
|
s.base2, err = snap.ParsePlaceInfoFromSnapFileName("core20_2.snap")
|
|
c.Assert(err, IsNil)
|
|
|
|
s.gadget1, err = snap.ParsePlaceInfoFromSnapFileName("pc_1.snap")
|
|
c.Assert(err, IsNil)
|
|
s.gadget2, err = snap.ParsePlaceInfoFromSnapFileName("pc_2.snap")
|
|
c.Assert(err, IsNil)
|
|
|
|
// default boot state for robustness tests, etc.
|
|
s.normalDefaultState = &bootenv20Setup{
|
|
modeenv: &boot.Modeenv{
|
|
// base is base1
|
|
Base: s.base1.Filename(),
|
|
// no try base
|
|
TryBase: "",
|
|
// base status is default
|
|
BaseStatus: boot.DefaultStatus,
|
|
// gadget is gadget1
|
|
Gadget: s.gadget1.Filename(),
|
|
// current kernels is just kern1
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
// operating mode is run
|
|
Mode: "run",
|
|
// RecoverySystem is unset, as it should be during run mode
|
|
RecoverySystem: "",
|
|
},
|
|
// enabled kernel is kern1
|
|
kern: s.kern1,
|
|
// no try kernel enabled
|
|
tryKern: nil,
|
|
// kernel status is default
|
|
kernStatus: boot.DefaultStatus,
|
|
}
|
|
|
|
// state for after trying a new kernel for robustness tests, etc.
|
|
s.normalTryingKernelState = &bootenv20Setup{
|
|
modeenv: &boot.Modeenv{
|
|
// operating mode is run
|
|
Mode: "run",
|
|
// base is base1
|
|
Base: s.base1.Filename(),
|
|
// no try base
|
|
TryBase: "",
|
|
// base status is default
|
|
BaseStatus: boot.DefaultStatus,
|
|
// gadget is gadget2
|
|
Gadget: s.gadget2.Filename(),
|
|
// current kernels is kern1 + kern2
|
|
CurrentKernels: []string{s.kern1.Filename(), s.kern2.Filename()},
|
|
},
|
|
// enabled kernel is kern1
|
|
kern: s.kern1,
|
|
// try kernel is kern2
|
|
tryKern: s.kern2,
|
|
// kernel status is trying
|
|
kernStatus: boot.TryingStatus,
|
|
}
|
|
|
|
s.mockCmdline(c, "snapd_recovery_mode=run")
|
|
}
|
|
|
|
type bootenv20Suite struct {
|
|
baseBootenv20Suite
|
|
|
|
bootloader *bootloadertest.MockExtractedRunKernelImageBootloader
|
|
}
|
|
|
|
type bootenv20EnvRefKernelSuite struct {
|
|
baseBootenv20Suite
|
|
|
|
bootloader *bootloadertest.MockBootloader
|
|
}
|
|
|
|
type bootenv20RebootBootloaderSuite struct {
|
|
baseBootenv20Suite
|
|
|
|
bootloader *bootloadertest.MockRebootBootloader
|
|
}
|
|
|
|
var _ = Suite(&bootenv20Suite{})
|
|
var _ = Suite(&bootenv20EnvRefKernelSuite{})
|
|
var _ = Suite(&bootenv20RebootBootloaderSuite{})
|
|
|
|
func (s *bootenv20Suite) SetUpTest(c *C) {
|
|
s.baseBootenv20Suite.SetUpTest(c)
|
|
|
|
s.bootloader = bootloadertest.Mock("mock", c.MkDir()).WithExtractedRunKernelImage()
|
|
s.forceBootloader(s.bootloader)
|
|
}
|
|
|
|
func (s *bootenv20EnvRefKernelSuite) SetUpTest(c *C) {
|
|
s.baseBootenv20Suite.SetUpTest(c)
|
|
|
|
s.bootloader = bootloadertest.Mock("mock", c.MkDir())
|
|
s.forceBootloader(s.bootloader)
|
|
}
|
|
|
|
func (s *bootenv20RebootBootloaderSuite) SetUpTest(c *C) {
|
|
s.baseBootenv20Suite.SetUpTest(c)
|
|
|
|
s.bootloader = bootloadertest.Mock("mock", c.MkDir()).WithRebootBootloader()
|
|
s.forceBootloader(s.bootloader)
|
|
}
|
|
|
|
type bootenv20Setup struct {
|
|
modeenv *boot.Modeenv
|
|
kern snap.PlaceInfo
|
|
tryKern snap.PlaceInfo
|
|
kernStatus string
|
|
}
|
|
|
|
func setupUC20Bootenv(c *C, bl bootloader.Bootloader, opts *bootenv20Setup) (restore func()) {
|
|
var cleanups []func()
|
|
|
|
// write the modeenv
|
|
if opts.modeenv != nil {
|
|
c.Assert(opts.modeenv.WriteTo(""), IsNil)
|
|
// this isn't strictly necessary since the modeenv will be written to
|
|
// the test's private dir anyways, but it's nice to have so we can write
|
|
// multiple modeenvs from a single test and just call the restore
|
|
// function in between the parts of the test that use different modeenvs
|
|
r := func() {
|
|
defaultModeenv := &boot.Modeenv{Mode: "run"}
|
|
c.Assert(defaultModeenv.WriteTo(""), IsNil)
|
|
}
|
|
cleanups = append(cleanups, r)
|
|
}
|
|
|
|
// set the status
|
|
origEnv, err := bl.GetBootVars("kernel_status")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = bl.SetBootVars(map[string]string{"kernel_status": opts.kernStatus})
|
|
c.Assert(err, IsNil)
|
|
cleanups = append(cleanups, func() {
|
|
err := bl.SetBootVars(origEnv)
|
|
c.Assert(err, IsNil)
|
|
})
|
|
|
|
// check what kind of real mock bootloader we have to use different methods
|
|
// to set the kernel snaps are if they're non-nil
|
|
switch vbl := bl.(type) {
|
|
case *bootloadertest.MockExtractedRunKernelImageBootloader:
|
|
// then we can use the advanced methods on it
|
|
if opts.kern != nil {
|
|
r := vbl.SetEnabledKernel(opts.kern)
|
|
cleanups = append(cleanups, r)
|
|
}
|
|
|
|
if opts.tryKern != nil {
|
|
r := vbl.SetEnabledTryKernel(opts.tryKern)
|
|
cleanups = append(cleanups, r)
|
|
}
|
|
|
|
// don't count any calls to SetBootVars made thus far
|
|
vbl.SetBootVarsCalls = 0
|
|
|
|
case *bootloadertest.MockBootloader:
|
|
// for non-extracted, we need to use the bootenv to set the current kernels
|
|
r := setupUC20MockBootloaderEnv(c, bl, opts)
|
|
cleanups = append(cleanups, r)
|
|
// don't count any calls to SetBootVars made thus far
|
|
vbl.SetBootVarsCalls = 0
|
|
case *bootloadertest.MockRebootBootloader:
|
|
// for non-extracted, we need to use the bootenv to set the current kernels
|
|
r := setupUC20MockBootloaderEnv(c, bl, opts)
|
|
cleanups = append(cleanups, r)
|
|
// don't count any calls to SetBootVars made thus far
|
|
vbl.SetBootVarsCalls = 0
|
|
default:
|
|
c.Fatalf("unsupported bootloader %T", bl)
|
|
}
|
|
|
|
return func() {
|
|
for _, r := range cleanups {
|
|
r()
|
|
}
|
|
}
|
|
}
|
|
|
|
func setupUC20MockBootloaderEnv(c *C, bl bootloader.Bootloader, opts *bootenv20Setup) (restore func()) {
|
|
origEnv, err := bl.GetBootVars("snap_kernel", "snap_try_kernel")
|
|
c.Assert(err, IsNil)
|
|
m := make(map[string]string, 2)
|
|
if opts.kern != nil {
|
|
m["snap_kernel"] = opts.kern.Filename()
|
|
} else {
|
|
m["snap_kernel"] = ""
|
|
}
|
|
|
|
if opts.tryKern != nil {
|
|
m["snap_try_kernel"] = opts.tryKern.Filename()
|
|
} else {
|
|
m["snap_try_kernel"] = ""
|
|
}
|
|
|
|
err = bl.SetBootVars(m)
|
|
c.Assert(err, IsNil)
|
|
|
|
return func() {
|
|
err := bl.SetBootVars(origEnv)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
}
|
|
|
|
func (s *bootenvSuite) TestInUseClassic(c *C) {
|
|
classicDev := boottest.MockDevice("")
|
|
|
|
// make bootloader.Find fail but shouldn't matter
|
|
bootloader.ForceError(errors.New("broken bootloader"))
|
|
|
|
inUse, err := boot.InUse(snap.TypeBase, classicDev)
|
|
c.Assert(err, IsNil)
|
|
c.Check(inUse("core18", snap.R(41)), Equals, false)
|
|
}
|
|
|
|
func (s *bootenvSuite) TestInUseIrrelevantTypes(c *C) {
|
|
coreDev := boottest.MockDevice("some-snap")
|
|
|
|
// make bootloader.Find fail but shouldn't matter
|
|
bootloader.ForceError(errors.New("broken bootloader"))
|
|
|
|
inUse, err := boot.InUse(snap.TypeGadget, coreDev)
|
|
c.Assert(err, IsNil)
|
|
c.Check(inUse("gadget", snap.R(41)), Equals, false)
|
|
}
|
|
|
|
func (s *bootenvSuite) TestInUse(c *C) {
|
|
coreDev := boottest.MockDevice("some-snap")
|
|
|
|
for _, t := range []struct {
|
|
bootVarKey string
|
|
bootVarValue string
|
|
|
|
snapName string
|
|
snapRev snap.Revision
|
|
|
|
inUse bool
|
|
}{
|
|
// in use
|
|
{"snap_kernel", "kernel_41.snap", "kernel", snap.R(41), true},
|
|
{"snap_try_kernel", "kernel_82.snap", "kernel", snap.R(82), true},
|
|
{"snap_core", "core_21.snap", "core", snap.R(21), true},
|
|
{"snap_try_core", "core_42.snap", "core", snap.R(42), true},
|
|
// not in use
|
|
{"snap_core", "core_111.snap", "core", snap.R(21), false},
|
|
{"snap_try_core", "core_111.snap", "core", snap.R(21), false},
|
|
{"snap_kernel", "kernel_111.snap", "kernel", snap.R(1), false},
|
|
{"snap_try_kernel", "kernel_111.snap", "kernel", snap.R(1), false},
|
|
} {
|
|
typ := snap.TypeBase
|
|
if t.snapName == "kernel" {
|
|
typ = snap.TypeKernel
|
|
}
|
|
s.bootloader.BootVars[t.bootVarKey] = t.bootVarValue
|
|
inUse, err := boot.InUse(typ, coreDev)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(inUse(t.snapName, t.snapRev), Equals, t.inUse, Commentf("unexpected result: %s %s %v", t.snapName, t.snapRev, t.inUse))
|
|
}
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestInUseCore20(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
c.Assert(coreDev.IsCoreBoot(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
&bootenv20Setup{
|
|
modeenv: &boot.Modeenv{
|
|
// base is base1
|
|
Base: s.base1.Filename(),
|
|
// no try base
|
|
TryBase: "",
|
|
// gadget is gadget1
|
|
Gadget: s.gadget1.Filename(),
|
|
// current kernels is just kern1
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
// operating mode is run
|
|
Mode: "run",
|
|
// RecoverySystem is unset, as it should be during run mode
|
|
RecoverySystem: "",
|
|
},
|
|
// enabled kernel is kern1
|
|
kern: s.kern1,
|
|
// no try kernel enabled
|
|
tryKern: nil,
|
|
// kernel status is default
|
|
kernStatus: boot.DefaultStatus,
|
|
})
|
|
defer r()
|
|
|
|
inUse, err := boot.InUse(snap.TypeKernel, coreDev)
|
|
c.Check(err, IsNil)
|
|
c.Check(inUse(s.kern1.SnapName(), s.kern1.SnapRevision()), Equals, true)
|
|
c.Check(inUse(s.kern2.SnapName(), s.kern2.SnapRevision()), Equals, false)
|
|
|
|
_, err = boot.InUse(snap.TypeBase, coreDev)
|
|
c.Check(err, IsNil)
|
|
}
|
|
|
|
func (s *bootenvSuite) TestInUseEphemeral(c *C) {
|
|
coreDev := boottest.MockDevice("some-snap@install")
|
|
|
|
// make bootloader.Find fail but shouldn't matter
|
|
bootloader.ForceError(errors.New("broken bootloader"))
|
|
|
|
inUse, err := boot.InUse(snap.TypeBase, coreDev)
|
|
c.Assert(err, IsNil)
|
|
c.Check(inUse("whatever", snap.R(0)), Equals, true)
|
|
}
|
|
|
|
func (s *bootenvSuite) TestInUseUnhappy(c *C) {
|
|
coreDev := boottest.MockDevice("some-snap")
|
|
|
|
// make GetVars fail
|
|
s.bootloader.GetErr = errors.New("zap")
|
|
_, err := boot.InUse(snap.TypeKernel, coreDev)
|
|
c.Check(err, ErrorMatches, `cannot get boot variables: zap`)
|
|
|
|
// make bootloader.Find fail
|
|
bootloader.ForceError(errors.New("broken bootloader"))
|
|
_, err = boot.InUse(snap.TypeKernel, coreDev)
|
|
c.Check(err, ErrorMatches, `cannot get boot settings: broken bootloader`)
|
|
}
|
|
|
|
func (s *bootenvSuite) TestCurrentBootNameAndRevision(c *C) {
|
|
coreDev := boottest.MockDevice("some-snap")
|
|
|
|
s.bootloader.BootVars["snap_core"] = "core_2.snap"
|
|
s.bootloader.BootVars["snap_kernel"] = "canonical-pc-linux_2.snap"
|
|
|
|
current, err := boot.GetCurrentBoot(snap.TypeOS, coreDev)
|
|
c.Check(err, IsNil)
|
|
c.Check(current.SnapName(), Equals, "core")
|
|
c.Check(current.SnapRevision(), Equals, snap.R(2))
|
|
|
|
current, err = boot.GetCurrentBoot(snap.TypeKernel, coreDev)
|
|
c.Check(err, IsNil)
|
|
c.Check(current.SnapName(), Equals, "canonical-pc-linux")
|
|
c.Check(current.SnapRevision(), Equals, snap.R(2))
|
|
|
|
s.bootloader.BootVars["snap_mode"] = boot.TryingStatus
|
|
_, err = boot.GetCurrentBoot(snap.TypeKernel, coreDev)
|
|
c.Check(err, Equals, boot.ErrBootNameAndRevisionNotReady)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCurrentBoot20NameAndRevision(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
s.normalDefaultState,
|
|
)
|
|
defer r()
|
|
|
|
current, err := boot.GetCurrentBoot(snap.TypeBase, coreDev)
|
|
c.Check(err, IsNil)
|
|
c.Check(current.SnapName(), Equals, s.base1.SnapName())
|
|
c.Check(current.SnapRevision(), Equals, snap.R(1))
|
|
|
|
current, err = boot.GetCurrentBoot(snap.TypeKernel, coreDev)
|
|
c.Check(err, IsNil)
|
|
c.Check(current.SnapName(), Equals, s.kern1.SnapName())
|
|
c.Check(current.SnapRevision(), Equals, snap.R(1))
|
|
|
|
s.bootloader.BootVars["kernel_status"] = boot.TryingStatus
|
|
_, err = boot.GetCurrentBoot(snap.TypeKernel, coreDev)
|
|
c.Check(err, Equals, boot.ErrBootNameAndRevisionNotReady)
|
|
}
|
|
|
|
// only difference between this test and TestCurrentBoot20NameAndRevision is the
|
|
// base bootloader which doesn't support ExtractedRunKernelImageBootloader.
|
|
func (s *bootenv20EnvRefKernelSuite) TestCurrentBoot20NameAndRevision(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
s.normalDefaultState,
|
|
)
|
|
defer r()
|
|
|
|
current, err := boot.GetCurrentBoot(snap.TypeKernel, coreDev)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(current.SnapName(), Equals, s.kern1.SnapName())
|
|
c.Assert(current.SnapRevision(), Equals, snap.R(1))
|
|
}
|
|
|
|
func (s *bootenvSuite) TestCurrentBootNameAndRevisionUnhappy(c *C) {
|
|
coreDev := boottest.MockDevice("some-snap")
|
|
|
|
_, err := boot.GetCurrentBoot(snap.TypeKernel, coreDev)
|
|
c.Check(err, ErrorMatches, `cannot get name and revision of kernel \(snap_kernel\): boot variable unset`)
|
|
|
|
_, err = boot.GetCurrentBoot(snap.TypeOS, coreDev)
|
|
c.Check(err, ErrorMatches, `cannot get name and revision of boot base \(snap_core\): boot variable unset`)
|
|
|
|
_, err = boot.GetCurrentBoot(snap.TypeBase, coreDev)
|
|
c.Check(err, ErrorMatches, `cannot get name and revision of boot base \(snap_core\): boot variable unset`)
|
|
|
|
_, err = boot.GetCurrentBoot(snap.TypeApp, coreDev)
|
|
c.Check(err, ErrorMatches, `internal error: no boot state handling for snap type "app"`)
|
|
|
|
// validity check
|
|
s.bootloader.BootVars["snap_kernel"] = "kernel_41.snap"
|
|
current, err := boot.GetCurrentBoot(snap.TypeKernel, coreDev)
|
|
c.Check(err, IsNil)
|
|
c.Check(current.SnapName(), Equals, "kernel")
|
|
c.Check(current.SnapRevision(), Equals, snap.R(41))
|
|
|
|
// make GetVars fail
|
|
s.bootloader.GetErr = errors.New("zap")
|
|
_, err = boot.GetCurrentBoot(snap.TypeKernel, coreDev)
|
|
c.Check(err, ErrorMatches, "cannot get boot variables: zap")
|
|
s.bootloader.GetErr = nil
|
|
|
|
// make bootloader.Find fail
|
|
bootloader.ForceError(errors.New("broken bootloader"))
|
|
_, err = boot.GetCurrentBoot(snap.TypeKernel, coreDev)
|
|
c.Check(err, ErrorMatches, "cannot get boot settings: broken bootloader")
|
|
}
|
|
|
|
func (s *bootenvSuite) TestSnapTypeParticipatesInBoot(c *C) {
|
|
classicDev := boottest.MockDevice("")
|
|
legacyCoreDev := boottest.MockDevice("some-snap")
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
coreDevInstallMode := boottest.MockUC20Device("install", nil)
|
|
|
|
for _, typ := range []snap.Type{
|
|
snap.TypeKernel,
|
|
snap.TypeOS,
|
|
snap.TypeBase,
|
|
} {
|
|
c.Check(boot.SnapTypeParticipatesInBoot(typ, classicDev), Equals, false)
|
|
c.Check(boot.SnapTypeParticipatesInBoot(typ, legacyCoreDev), Equals, true)
|
|
c.Check(boot.SnapTypeParticipatesInBoot(typ, coreDev), Equals, true)
|
|
c.Check(boot.SnapTypeParticipatesInBoot(typ, coreDevInstallMode), Equals, true)
|
|
}
|
|
|
|
classicWithModesDev := boottest.MockClassicWithModesDevice("", nil)
|
|
c.Check(boot.SnapTypeParticipatesInBoot(snap.TypeKernel, classicWithModesDev), Equals, true)
|
|
c.Check(boot.SnapTypeParticipatesInBoot(snap.TypeOS, classicWithModesDev), Equals, false)
|
|
c.Check(boot.SnapTypeParticipatesInBoot(snap.TypeBase, classicWithModesDev), Equals, false)
|
|
|
|
classicWithModesDevInstallMode := boottest.MockClassicWithModesDevice("install", nil)
|
|
c.Check(boot.SnapTypeParticipatesInBoot(snap.TypeKernel, classicWithModesDevInstallMode), Equals, true)
|
|
}
|
|
|
|
func (s *bootenvSuite) TestParticipant(c *C) {
|
|
info := &snap.Info{}
|
|
info.RealName = "some-snap"
|
|
|
|
coreDev := boottest.MockDevice("some-snap")
|
|
classicDev := boottest.MockDevice("")
|
|
|
|
bp := boot.Participant(info, snap.TypeApp, coreDev)
|
|
c.Check(bp.IsTrivial(), Equals, true)
|
|
|
|
for _, typ := range []snap.Type{
|
|
snap.TypeKernel,
|
|
snap.TypeOS,
|
|
snap.TypeBase,
|
|
} {
|
|
bp = boot.Participant(info, typ, classicDev)
|
|
c.Check(bp.IsTrivial(), Equals, true)
|
|
|
|
bp = boot.Participant(info, typ, coreDev)
|
|
c.Check(bp.IsTrivial(), Equals, false)
|
|
|
|
c.Check(bp, DeepEquals, boot.NewCoreBootParticipant(info, typ, coreDev))
|
|
}
|
|
}
|
|
|
|
func (s *bootenvSuite) TestParticipantBaseWithModel(c *C) {
|
|
core := &snap.Info{SideInfo: snap.SideInfo{RealName: "core"}, SnapType: snap.TypeOS}
|
|
core18 := &snap.Info{SideInfo: snap.SideInfo{RealName: "core18"}, SnapType: snap.TypeBase}
|
|
core20 := &snap.Info{SideInfo: snap.SideInfo{RealName: "core20"}, SnapType: snap.TypeBase}
|
|
|
|
type tableT struct {
|
|
with *snap.Info
|
|
model string
|
|
nop bool
|
|
}
|
|
|
|
table := []tableT{
|
|
{
|
|
with: core,
|
|
model: "",
|
|
nop: true,
|
|
}, {
|
|
with: core,
|
|
model: "core",
|
|
nop: false,
|
|
}, {
|
|
with: core,
|
|
model: "core18",
|
|
nop: true,
|
|
},
|
|
{
|
|
with: core18,
|
|
model: "",
|
|
nop: true,
|
|
},
|
|
{
|
|
with: core18,
|
|
model: "core",
|
|
nop: true,
|
|
},
|
|
{
|
|
with: core18,
|
|
model: "core18",
|
|
nop: false,
|
|
},
|
|
{
|
|
with: core18,
|
|
model: "core18@install",
|
|
nop: true,
|
|
},
|
|
{
|
|
with: core,
|
|
model: "core@install",
|
|
nop: true,
|
|
},
|
|
{
|
|
with: core20,
|
|
model: "core@run",
|
|
nop: true,
|
|
},
|
|
}
|
|
|
|
for i, t := range table {
|
|
dev := boottest.MockDevice(t.model)
|
|
bp := boot.Participant(t.with, t.with.Type(), dev)
|
|
c.Check(bp.IsTrivial(), Equals, t.nop, Commentf("%d", i))
|
|
if !t.nop {
|
|
c.Check(bp, DeepEquals, boot.NewCoreBootParticipant(t.with, t.with.Type(), dev))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *bootenvSuite) TestParticipantGadgetWithModel(c *C) {
|
|
gadget := &snap.Info{SideInfo: snap.SideInfo{RealName: "pc"}, SnapType: snap.TypeGadget}
|
|
|
|
type tableT struct {
|
|
with *snap.Info
|
|
model string
|
|
nop bool
|
|
}
|
|
|
|
table := []tableT{
|
|
{
|
|
with: gadget,
|
|
model: "",
|
|
nop: true,
|
|
}, {
|
|
with: gadget,
|
|
model: "pc",
|
|
nop: true,
|
|
}, {
|
|
with: gadget,
|
|
model: "pc@run",
|
|
nop: false,
|
|
}, {
|
|
with: gadget,
|
|
model: "other-gadget",
|
|
nop: true,
|
|
},
|
|
{
|
|
with: gadget,
|
|
model: "pc@install",
|
|
nop: true,
|
|
},
|
|
}
|
|
|
|
for i, t := range table {
|
|
dev := boottest.MockDevice(t.model)
|
|
bp := boot.Participant(t.with, t.with.Type(), dev)
|
|
c.Check(bp.IsTrivial(), Equals, t.nop, Commentf("%d", i))
|
|
if !t.nop {
|
|
c.Check(bp, DeepEquals, boot.NewCoreBootParticipant(t.with, t.with.Type(), dev))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *bootenvSuite) TestKernelWithModel(c *C) {
|
|
info := &snap.Info{}
|
|
info.RealName = "kernel"
|
|
|
|
type tableT struct {
|
|
model string
|
|
nop bool
|
|
krn boot.BootKernel
|
|
}
|
|
|
|
table := []tableT{
|
|
{
|
|
model: "other-kernel",
|
|
nop: true,
|
|
krn: boot.Trivial{},
|
|
}, {
|
|
model: "kernel",
|
|
nop: false,
|
|
krn: boot.NewCoreKernel(info, boottest.MockDevice("kernel")),
|
|
}, {
|
|
model: "",
|
|
nop: true,
|
|
krn: boot.Trivial{},
|
|
}, {
|
|
model: "kernel@install",
|
|
nop: true,
|
|
krn: boot.Trivial{},
|
|
},
|
|
}
|
|
|
|
for _, t := range table {
|
|
dev := boottest.MockDevice(t.model)
|
|
krn := boot.Kernel(info, snap.TypeKernel, dev)
|
|
c.Check(krn.IsTrivial(), Equals, t.nop)
|
|
c.Check(krn, DeepEquals, t.krn)
|
|
}
|
|
}
|
|
|
|
func (s *bootenvSuite) TestParticipantClassicWithModesWithModel(c *C) {
|
|
modelHdrs := map[string]interface{}{
|
|
"type": "model",
|
|
"authority-id": "brand",
|
|
"series": "16",
|
|
"brand-id": "brand",
|
|
"model": "baz-3000",
|
|
"architecture": "amd64",
|
|
"classic": "true",
|
|
"distribution": "ubuntu",
|
|
"base": "core22",
|
|
"snaps": []interface{}{
|
|
map[string]interface{}{
|
|
"name": "kernel",
|
|
"id": "pclinuxdidididididididididididid",
|
|
"type": "kernel",
|
|
},
|
|
map[string]interface{}{
|
|
"name": "gadget",
|
|
"id": "pcididididididididididididididid",
|
|
"type": "gadget",
|
|
},
|
|
},
|
|
}
|
|
model := assertstest.FakeAssertion(modelHdrs).(*asserts.Model)
|
|
classicWithModesDev := boottest.MockClassicWithModesDevice("", model)
|
|
|
|
tests := []struct {
|
|
name string
|
|
typ snap.Type
|
|
nonTrivial bool
|
|
}{
|
|
{"some-snap", snap.TypeApp, false},
|
|
{"core22", snap.TypeBase, false},
|
|
{"kernel", snap.TypeKernel, true},
|
|
{"gadget", snap.TypeGadget, true},
|
|
}
|
|
|
|
for _, t := range tests {
|
|
info := &snap.Info{}
|
|
info.RealName = t.name
|
|
|
|
bp := boot.Participant(info, t.typ, classicWithModesDev)
|
|
if !t.nonTrivial {
|
|
c.Check(bp.IsTrivial(), Equals, true)
|
|
} else {
|
|
c.Check(bp, DeepEquals, boot.NewCoreBootParticipant(info, t.typ, classicWithModesDev))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *bootenvSuite) TestMarkBootSuccessfulKernelStatusTryingNoTryKernelSnapCleansUp(c *C) {
|
|
coreDev := boottest.MockDevice("some-snap")
|
|
|
|
// set all the same vars as if we were doing trying, except don't set a try
|
|
// kernel
|
|
|
|
err := s.bootloader.SetBootVars(map[string]string{
|
|
"snap_kernel": "kernel_41.snap",
|
|
"snap_mode": boot.TryingStatus,
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
// mark successful
|
|
err = boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check that the bootloader variables were cleaned
|
|
expected := map[string]string{
|
|
"snap_mode": boot.DefaultStatus,
|
|
"snap_kernel": "kernel_41.snap",
|
|
"snap_try_kernel": "",
|
|
}
|
|
m, err := s.bootloader.GetBootVars("snap_mode", "snap_try_kernel", "snap_kernel")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m, DeepEquals, expected)
|
|
|
|
// do it again, verify it's still okay
|
|
err = boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
m2, err := s.bootloader.GetBootVars("snap_mode", "snap_try_kernel", "snap_kernel")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2, DeepEquals, expected)
|
|
}
|
|
|
|
func (s *bootenvSuite) TestMarkBootSuccessfulTryKernelKernelStatusDefaultCleansUp(c *C) {
|
|
coreDev := boottest.MockDevice("some-snap")
|
|
|
|
// set an errant snap_try_kernel
|
|
err := s.bootloader.SetBootVars(map[string]string{
|
|
"snap_kernel": "kernel_41.snap",
|
|
"snap_try_kernel": "kernel_42.snap",
|
|
"snap_mode": boot.DefaultStatus,
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
// mark successful
|
|
err = boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check that the bootloader variables were cleaned
|
|
expected := map[string]string{
|
|
"snap_mode": boot.DefaultStatus,
|
|
"snap_kernel": "kernel_41.snap",
|
|
"snap_try_kernel": "",
|
|
}
|
|
m, err := s.bootloader.GetBootVars("snap_mode", "snap_try_kernel", "snap_kernel")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m, DeepEquals, expected)
|
|
|
|
// do it again, verify it's still okay
|
|
err = boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
m2, err := s.bootloader.GetBootVars("snap_mode", "snap_try_kernel", "snap_kernel")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2, DeepEquals, expected)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreKernel20(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
s.normalDefaultState,
|
|
)
|
|
defer r()
|
|
|
|
// get the boot kernel from our kernel snap
|
|
bootKern := boot.Kernel(s.kern1, snap.TypeKernel, coreDev)
|
|
// can't use FitsTypeOf with coreKernel here, cause that causes an import
|
|
// loop as boottest imports boot and coreKernel is unexported
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// extract the kernel assets from the coreKernel
|
|
// the container here doesn't really matter since it's just being passed
|
|
// to the mock bootloader method anyways
|
|
kernelContainer := snaptest.MockContainer(c, nil)
|
|
err := bootKern.ExtractKernelAssets(kernelContainer)
|
|
c.Assert(err, IsNil)
|
|
|
|
// make sure that the bootloader was told to extract some assets
|
|
c.Assert(s.bootloader.ExtractKernelAssetsCalls, DeepEquals, []snap.PlaceInfo{s.kern1})
|
|
|
|
// now remove the kernel assets and ensure that we get those calls
|
|
err = bootKern.RemoveKernelAssets()
|
|
c.Assert(err, IsNil)
|
|
|
|
// make sure that the bootloader was told to remove assets
|
|
c.Assert(s.bootloader.RemoveKernelAssetsCalls, DeepEquals, []snap.PlaceInfo{s.kern1})
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20SetNextSameKernelSnap(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
s.normalDefaultState,
|
|
)
|
|
defer r()
|
|
|
|
// get the boot kernel participant from our kernel snap
|
|
bootKern := boot.Participant(s.kern1, snap.TypeKernel, coreDev)
|
|
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// make the kernel used on next boot
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: false})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: false})
|
|
|
|
// make sure that the bootloader was asked for the current kernel
|
|
_, nKernelCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("Kernel")
|
|
c.Assert(nKernelCalls, Equals, 1)
|
|
|
|
// ensure that kernel_status is still empty
|
|
c.Assert(s.bootloader.BootVars["kernel_status"], Equals, boot.DefaultStatus)
|
|
|
|
// there was no attempt to enable a kernel
|
|
_, enableKernelCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableTryKernel")
|
|
c.Assert(enableKernelCalls, Equals, 0)
|
|
|
|
// the modeenv is still the same as well
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename()})
|
|
|
|
// finally we didn't call SetBootVars on the bootloader because nothing
|
|
// changed
|
|
c.Assert(s.bootloader.SetBootVarsCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootenv20EnvRefKernelSuite) TestCoreParticipant20SetNextSameKernelSnap(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
s.normalDefaultState,
|
|
)
|
|
defer r()
|
|
|
|
// get the boot kernel participant from our kernel snap
|
|
bootKern := boot.Participant(s.kern1, snap.TypeKernel, coreDev)
|
|
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// make the kernel used on next boot
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: false})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: false})
|
|
|
|
// ensure that bootenv is unchanged
|
|
m, err := s.bootloader.GetBootVars("kernel_status", "snap_kernel", "snap_try_kernel")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m, DeepEquals, map[string]string{
|
|
"kernel_status": boot.DefaultStatus,
|
|
"snap_kernel": s.kern1.Filename(),
|
|
"snap_try_kernel": "",
|
|
})
|
|
|
|
// the modeenv is still the same as well
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename()})
|
|
|
|
// finally we didn't call SetBootVars on the bootloader because nothing
|
|
// changed
|
|
c.Assert(s.bootloader.SetBootVarsCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20SetNextNewKernelSnap(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
s.normalDefaultState,
|
|
)
|
|
defer r()
|
|
|
|
// get the boot kernel participant from our new kernel snap
|
|
bootKern := boot.Participant(s.kern2, snap.TypeKernel, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// make the kernel used on next boot
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: false})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, DeepEquals, boot.RebootInfo{
|
|
RebootRequired: true,
|
|
BootloaderOptions: &bootloader.Options{
|
|
Role: bootloader.RoleRunMode,
|
|
},
|
|
})
|
|
|
|
// make sure that the bootloader was asked for the current kernel
|
|
_, nKernelCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("Kernel")
|
|
c.Assert(nKernelCalls, Equals, 1)
|
|
|
|
// ensure that kernel_status is now try
|
|
c.Assert(s.bootloader.BootVars["kernel_status"], Equals, boot.TryStatus)
|
|
|
|
// and we were asked to enable kernel2 as the try kernel
|
|
actual, _ := s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableTryKernel")
|
|
c.Assert(actual, DeepEquals, []snap.PlaceInfo{s.kern2})
|
|
|
|
// and that the modeenv now has this kernel listed
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename(), s.kern2.Filename()})
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20SetNextNewKernelSnapWithReseal(c *C) {
|
|
// checked by resealKeyToModeenv
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
|
|
data := []byte("foobar")
|
|
// SHA3-384
|
|
dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
|
|
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil)
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir), 0755), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil)
|
|
|
|
// mock the files in cache
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"asset-" + dataHash,
|
|
})
|
|
|
|
assetBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)), bootloader.RoleRunMode)
|
|
runKernelBf := bootloader.NewBootFile(filepath.Join(s.kern1.Filename()), "kernel.efi", bootloader.RoleRunMode)
|
|
|
|
tab.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
|
|
// TODO:UC20: fix mocked trusted assets bootloader to actually
|
|
// geenerate kernel boot files
|
|
runKernelBf,
|
|
}
|
|
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
model := coreDev.Model()
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
},
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
resealCalls := 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
|
|
c.Assert(params.ModelParams, HasLen, 1)
|
|
mp := params.ModelParams[0]
|
|
c.Check(mp.Model.Model(), Equals, model.Model())
|
|
for _, ch := range mp.EFILoadChains {
|
|
printChain(c, ch, "-")
|
|
}
|
|
c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{
|
|
secboot.NewLoadChain(assetBf,
|
|
secboot.NewLoadChain(runKernelBf)),
|
|
secboot.NewLoadChain(assetBf,
|
|
// TODO:UC20: once mock trusted assets
|
|
// bootloader can generated boot files for the
|
|
// kernel this will use candidate kernel
|
|
secboot.NewLoadChain(runKernelBf)),
|
|
})
|
|
// actual paths are seen only here
|
|
c.Check(tab.BootChainKernelPath, DeepEquals, []string{
|
|
s.kern1.MountFile(),
|
|
s.kern2.MountFile(),
|
|
})
|
|
return nil
|
|
})
|
|
defer restore()
|
|
|
|
// get the boot kernel participant from our new kernel snap
|
|
bootKern := boot.Participant(s.kern2, snap.TypeKernel, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// make the kernel used on next boot
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: false})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, DeepEquals, boot.RebootInfo{
|
|
RebootRequired: true,
|
|
BootloaderOptions: &bootloader.Options{
|
|
Role: bootloader.RoleRunMode,
|
|
},
|
|
})
|
|
|
|
// make sure the env was updated
|
|
bvars, err := tab.GetBootVars("kernel_status", "snap_kernel", "snap_try_kernel")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(bvars, DeepEquals, map[string]string{
|
|
"kernel_status": boot.TryStatus,
|
|
"snap_kernel": s.kern1.Filename(),
|
|
"snap_try_kernel": s.kern2.Filename(),
|
|
})
|
|
|
|
// and that the modeenv now has this kernel listed
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename(), s.kern2.Filename()})
|
|
|
|
c.Check(resealCalls, Equals, 1)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20SetNextNewUnassertedKernelSnapWithReseal(c *C) {
|
|
// checked by resealKeyToModeenv
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
|
|
data := []byte("foobar")
|
|
// SHA3-384
|
|
dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
|
|
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil)
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir), 0755), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil)
|
|
|
|
// mock the files in cache
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"asset-" + dataHash,
|
|
})
|
|
|
|
assetBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)), bootloader.RoleRunMode)
|
|
runKernelBf := bootloader.NewBootFile(filepath.Join(s.ukern1.Filename()), "kernel.efi", bootloader.RoleRunMode)
|
|
|
|
tab.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
|
|
// TODO:UC20: fix mocked trusted assets bootloader to actually
|
|
// geenerate kernel boot files
|
|
runKernelBf,
|
|
}
|
|
|
|
uc20Model := boottest.MakeMockUC20Model()
|
|
coreDev := boottest.MockUC20Device("", uc20Model)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.ukern1.Filename()},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
},
|
|
Model: uc20Model.Model(),
|
|
BrandID: uc20Model.BrandID(),
|
|
Grade: string(uc20Model.Grade()),
|
|
ModelSignKeyID: uc20Model.SignKeyID(),
|
|
}
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.ukern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
resealCalls := 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
|
|
c.Assert(params.ModelParams, HasLen, 1)
|
|
mp := params.ModelParams[0]
|
|
c.Check(mp.Model.Model(), Equals, uc20Model.Model())
|
|
for _, ch := range mp.EFILoadChains {
|
|
printChain(c, ch, "-")
|
|
}
|
|
c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{
|
|
secboot.NewLoadChain(assetBf,
|
|
secboot.NewLoadChain(runKernelBf)),
|
|
secboot.NewLoadChain(assetBf,
|
|
// TODO:UC20: once mock trusted assets
|
|
// bootloader can generated boot files for the
|
|
// kernel this will use candidate kernel
|
|
secboot.NewLoadChain(runKernelBf)),
|
|
})
|
|
// actual paths are seen only here
|
|
c.Check(tab.BootChainKernelPath, DeepEquals, []string{
|
|
s.ukern1.MountFile(),
|
|
s.ukern2.MountFile(),
|
|
})
|
|
return nil
|
|
})
|
|
defer restore()
|
|
|
|
// get the boot kernel participant from our new kernel snap
|
|
bootKern := boot.Participant(s.ukern2, snap.TypeKernel, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// make the kernel used on next boot
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: false})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, DeepEquals, boot.RebootInfo{
|
|
RebootRequired: true,
|
|
BootloaderOptions: &bootloader.Options{
|
|
Role: bootloader.RoleRunMode,
|
|
},
|
|
})
|
|
|
|
bvars, err := tab.GetBootVars("kernel_status", "snap_kernel", "snap_try_kernel")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(bvars, DeepEquals, map[string]string{
|
|
"kernel_status": boot.TryStatus,
|
|
"snap_kernel": s.ukern1.Filename(),
|
|
"snap_try_kernel": s.ukern2.Filename(),
|
|
})
|
|
|
|
// and that the modeenv now has this kernel listed
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.ukern1.Filename(), s.ukern2.Filename()})
|
|
|
|
c.Check(resealCalls, Equals, 1)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20SetNextSameKernelSnapNoReseal(c *C) {
|
|
// checked by resealKeyToModeenv
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
|
|
data := []byte("foobar")
|
|
// SHA3-384
|
|
dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
|
|
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil)
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir), 0755), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil)
|
|
|
|
// mock the files in cache
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"asset-" + dataHash,
|
|
})
|
|
|
|
runKernelBf := bootloader.NewBootFile(filepath.Join(s.kern1.Filename()), "kernel.efi", bootloader.RoleRunMode)
|
|
|
|
tab.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
|
|
runKernelBf,
|
|
}
|
|
|
|
uc20Model := boottest.MakeMockUC20Model()
|
|
coreDev := boottest.MockUC20Device("", uc20Model)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
},
|
|
CurrentKernelCommandLines: boot.BootCommandLines{"snapd_recovery_mode=run"},
|
|
|
|
Model: uc20Model.Model(),
|
|
BrandID: uc20Model.BrandID(),
|
|
Grade: string(uc20Model.Grade()),
|
|
ModelSignKeyID: uc20Model.SignKeyID(),
|
|
}
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
resealCalls := 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
return fmt.Errorf("unexpected call")
|
|
})
|
|
defer restore()
|
|
|
|
// get the boot kernel participant from our kernel snap
|
|
bootKern := boot.Participant(s.kern1, snap.TypeKernel, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// write boot-chains for current state that will stay unchanged
|
|
bootChains := []boot.BootChain{{
|
|
BrandID: "my-brand",
|
|
Model: "my-model-uc20",
|
|
Grade: "dangerous",
|
|
ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
|
|
AssetChain: []boot.BootAsset{
|
|
{
|
|
Role: bootloader.RoleRunMode,
|
|
Name: "asset",
|
|
Hashes: []string{
|
|
"0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8",
|
|
},
|
|
},
|
|
},
|
|
Kernel: "pc-kernel",
|
|
KernelRevision: "1",
|
|
KernelCmdlines: []string{"snapd_recovery_mode=run"},
|
|
}}
|
|
err := boot.WriteBootChains(bootChains, filepath.Join(dirs.SnapFDEDir, "boot-chains"), 0)
|
|
c.Assert(err, IsNil)
|
|
|
|
// make the kernel used on next boot
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: false})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: false})
|
|
|
|
// make sure the env is as expected
|
|
bvars, err := tab.GetBootVars("kernel_status", "snap_kernel", "snap_try_kernel")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(bvars, DeepEquals, map[string]string{
|
|
"kernel_status": boot.DefaultStatus,
|
|
"snap_kernel": s.kern1.Filename(),
|
|
"snap_try_kernel": "",
|
|
})
|
|
|
|
// and that the modeenv now has the one kernel listed
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename()})
|
|
|
|
// boot chains were built
|
|
c.Check(tab.BootChainKernelPath, DeepEquals, []string{
|
|
s.kern1.MountFile(),
|
|
})
|
|
// no actual reseal
|
|
c.Check(resealCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20SetNextSameUnassertedKernelSnapNoReseal(c *C) {
|
|
// checked by resealKeyToModeenv
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
|
|
data := []byte("foobar")
|
|
// SHA3-384
|
|
dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
|
|
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil)
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir), 0755), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil)
|
|
|
|
// mock the files in cache
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"asset-" + dataHash,
|
|
})
|
|
|
|
runKernelBf := bootloader.NewBootFile(filepath.Join(s.ukern1.Filename()), "kernel.efi", bootloader.RoleRunMode)
|
|
|
|
tab.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
|
|
runKernelBf,
|
|
}
|
|
|
|
uc20Model := boottest.MakeMockUC20Model()
|
|
coreDev := boottest.MockUC20Device("", uc20Model)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.ukern1.Filename()},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
},
|
|
CurrentKernelCommandLines: boot.BootCommandLines{"snapd_recovery_mode=run"},
|
|
|
|
Model: uc20Model.Model(),
|
|
BrandID: uc20Model.BrandID(),
|
|
Grade: string(uc20Model.Grade()),
|
|
ModelSignKeyID: uc20Model.SignKeyID(),
|
|
}
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.ukern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
resealCalls := 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
return fmt.Errorf("unexpected call")
|
|
})
|
|
defer restore()
|
|
|
|
// get the boot kernel participant from our kernel snap
|
|
bootKern := boot.Participant(s.ukern1, snap.TypeKernel, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// write boot-chains for current state that will stay unchanged
|
|
bootChains := []boot.BootChain{{
|
|
BrandID: "my-brand",
|
|
Model: "my-model-uc20",
|
|
Grade: "dangerous",
|
|
ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
|
|
AssetChain: []boot.BootAsset{
|
|
{
|
|
Role: bootloader.RoleRunMode,
|
|
Name: "asset",
|
|
Hashes: []string{
|
|
"0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8",
|
|
},
|
|
},
|
|
},
|
|
Kernel: "pc-kernel",
|
|
KernelRevision: "",
|
|
KernelCmdlines: []string{"snapd_recovery_mode=run"},
|
|
}}
|
|
err := boot.WriteBootChains(bootChains, filepath.Join(dirs.SnapFDEDir, "boot-chains"), 0)
|
|
c.Assert(err, IsNil)
|
|
|
|
// make the kernel used on next boot
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: false})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: false})
|
|
|
|
// make sure the env is as expected
|
|
bvars, err := tab.GetBootVars("kernel_status", "snap_kernel", "snap_try_kernel")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(bvars, DeepEquals, map[string]string{
|
|
"kernel_status": boot.DefaultStatus,
|
|
"snap_kernel": s.ukern1.Filename(),
|
|
"snap_try_kernel": "",
|
|
})
|
|
|
|
// and that the modeenv now has the one kernel listed
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.ukern1.Filename()})
|
|
|
|
// boot chains were built
|
|
c.Check(tab.BootChainKernelPath, DeepEquals, []string{
|
|
s.ukern1.MountFile(),
|
|
})
|
|
// no actual reseal
|
|
c.Check(resealCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootenv20EnvRefKernelSuite) TestCoreParticipant20SetNextNewKernelSnap(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
s.normalDefaultState,
|
|
)
|
|
defer r()
|
|
|
|
// get the boot kernel participant from our new kernel snap
|
|
bootKern := boot.Participant(s.kern2, snap.TypeKernel, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// make the kernel used on next boot
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: false})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, DeepEquals, boot.RebootInfo{
|
|
RebootRequired: true,
|
|
BootloaderOptions: &bootloader.Options{
|
|
Role: bootloader.RoleRunMode,
|
|
},
|
|
})
|
|
|
|
// make sure the env was updated
|
|
m := s.bootloader.BootVars
|
|
c.Assert(m, DeepEquals, map[string]string{
|
|
"kernel_status": boot.TryStatus,
|
|
"snap_kernel": s.kern1.Filename(),
|
|
"snap_try_kernel": s.kern2.Filename(),
|
|
})
|
|
|
|
// and that the modeenv now has this kernel listed
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename(), s.kern2.Filename()})
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20KernelStatusTryingNoKernelSnapCleansUp(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
// set all the same vars as if we were doing trying, except don't set a try
|
|
// kernel
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
&bootenv20Setup{
|
|
modeenv: &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename(), s.kern2.Filename()},
|
|
},
|
|
kern: s.kern1,
|
|
// no try-kernel
|
|
kernStatus: boot.TryingStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check that the bootloader variable was cleaned
|
|
expected := map[string]string{"kernel_status": boot.DefaultStatus}
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, expected)
|
|
|
|
// check that MarkBootSuccessful didn't enable a kernel (since there was no
|
|
// try kernel)
|
|
_, nEnableCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableKernel")
|
|
c.Assert(nEnableCalls, Equals, 0)
|
|
|
|
// we will always end up disabling a try-kernel though as cleanup
|
|
_, nDisableTryCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel")
|
|
c.Assert(nDisableTryCalls, Equals, 1)
|
|
|
|
// do it again, verify it's still okay
|
|
err = boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, expected)
|
|
|
|
// no new enabled kernels
|
|
_, nEnableCalls = s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableKernel")
|
|
c.Assert(nEnableCalls, Equals, 0)
|
|
|
|
// again we will try to cleanup any leftover try-kernels
|
|
_, nDisableTryCalls = s.bootloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel")
|
|
c.Assert(nDisableTryCalls, Equals, 2)
|
|
|
|
// check that the modeenv re-wrote the CurrentKernels
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename()})
|
|
}
|
|
|
|
func (s *bootenv20EnvRefKernelSuite) TestMarkBootSuccessful20KernelStatusTryingNoKernelSnapCleansUp(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
// set all the same vars as if we were doing trying, except don't set a try
|
|
// kernel
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
&bootenv20Setup{
|
|
modeenv: &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename(), s.kern2.Filename()},
|
|
},
|
|
kern: s.kern1,
|
|
// no try-kernel
|
|
kernStatus: boot.TryingStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// make sure the env was updated
|
|
expected := map[string]string{
|
|
"kernel_status": boot.DefaultStatus,
|
|
"snap_kernel": s.kern1.Filename(),
|
|
"snap_try_kernel": "",
|
|
}
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, expected)
|
|
|
|
// do it again, verify it's still okay
|
|
err = boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, expected)
|
|
|
|
// check that the modeenv re-wrote the CurrentKernels
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename()})
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20BaseStatusTryingNoTryBaseSnapCleansUp(c *C) {
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
// no TryBase set
|
|
BaseStatus: boot.TryingStatus,
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
// no kernel setup necessary
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check that the modeenv base_status was re-written to default
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.BaseStatus, Equals, boot.DefaultStatus)
|
|
c.Assert(m2.Base, Equals, m.Base)
|
|
c.Assert(m2.TryBase, Equals, m.TryBase)
|
|
|
|
// do it again, verify it's still okay
|
|
err = boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
m3, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m3.BaseStatus, Equals, boot.DefaultStatus)
|
|
c.Assert(m3.Base, Equals, m.Base)
|
|
c.Assert(m3.TryBase, Equals, m.TryBase)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20SetNextSameBaseSnap(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
// no kernel setup necessary
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// get the boot base participant from our base snap
|
|
bootBase := boot.Participant(s.base1, snap.TypeBase, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootBase.IsTrivial(), Equals, false)
|
|
|
|
// make the base used on next boot
|
|
rebootRequired, err := bootBase.SetNextBoot(boot.NextBootContext{BootWithoutTry: false})
|
|
c.Assert(err, IsNil)
|
|
|
|
// we don't need to reboot because it's the same base snap
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: false})
|
|
|
|
// make sure the modeenv wasn't changed
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.Base, Equals, m.Base)
|
|
c.Assert(m2.BaseStatus, Equals, m.BaseStatus)
|
|
c.Assert(m2.TryBase, Equals, m.TryBase)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20SetNextNewBaseSnap(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
// default state
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
// no kernel setup necessary
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// get the boot base participant from our new base snap
|
|
bootBase := boot.Participant(s.base2, snap.TypeBase, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootBase.IsTrivial(), Equals, false)
|
|
|
|
// make the base used on next boot
|
|
rebootRequired, err := bootBase.SetNextBoot(boot.NextBootContext{BootWithoutTry: false})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: true})
|
|
|
|
// make sure the modeenv was updated
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.Base, Equals, m.Base)
|
|
c.Assert(m2.BaseStatus, Equals, boot.TryStatus)
|
|
c.Assert(m2.TryBase, Equals, s.base2.Filename())
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20SetNextNewBaseSnapNoReseal(c *C) {
|
|
// checked by resealKeyToModeenv
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
// set up all the bits required for an encrypted system
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
data := []byte("foobar")
|
|
// SHA3-384
|
|
dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
|
|
// mock the files in cache
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"asset-" + dataHash,
|
|
})
|
|
runKernelBf := bootloader.NewBootFile(filepath.Join(s.kern1.Filename()), "kernel.efi", bootloader.RoleRunMode)
|
|
// write boot-chains for current state that will stay unchanged even
|
|
// though base is changed
|
|
bootChains := []boot.BootChain{{
|
|
BrandID: "my-brand",
|
|
Model: "my-model-uc20",
|
|
Grade: "dangerous",
|
|
ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
|
|
AssetChain: []boot.BootAsset{{
|
|
Role: bootloader.RoleRunMode, Name: "asset", Hashes: []string{
|
|
"0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8",
|
|
},
|
|
}},
|
|
Kernel: "pc-kernel",
|
|
KernelRevision: "1",
|
|
KernelCmdlines: []string{"snapd_recovery_mode=run"},
|
|
}}
|
|
|
|
err := boot.WriteBootChains(boot.ToPredictableBootChains(bootChains), filepath.Join(dirs.SnapFDEDir, "boot-chains"), 0)
|
|
c.Assert(err, IsNil)
|
|
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
model := coreDev.Model()
|
|
|
|
resealCalls := 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
return nil
|
|
})
|
|
defer restore()
|
|
|
|
tab.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
|
|
runKernelBf,
|
|
}
|
|
|
|
// default state
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
},
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
// no kernel setup necessary
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// get the boot base participant from our new base snap
|
|
bootBase := boot.Participant(s.base2, snap.TypeBase, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootBase.IsTrivial(), Equals, false)
|
|
|
|
// make the base used on next boot
|
|
rebootRequired, err := bootBase.SetNextBoot(boot.NextBootContext{BootWithoutTry: false})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: true})
|
|
|
|
// make sure the modeenv was updated
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.Base, Equals, m.Base)
|
|
c.Assert(m2.BaseStatus, Equals, boot.TryStatus)
|
|
c.Assert(m2.TryBase, Equals, s.base2.Filename())
|
|
|
|
// no reseal
|
|
c.Check(resealCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootenvSuite) TestMarkBootSuccessfulAllSnap(c *C) {
|
|
coreDev := boottest.MockDevice("some-snap")
|
|
|
|
s.bootloader.BootVars["snap_mode"] = boot.TryingStatus
|
|
s.bootloader.BootVars["snap_try_core"] = "os1"
|
|
s.bootloader.BootVars["snap_try_kernel"] = "k1"
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
expected := map[string]string{
|
|
// cleared
|
|
"snap_mode": boot.DefaultStatus,
|
|
"snap_try_kernel": "",
|
|
"snap_try_core": "",
|
|
// updated
|
|
"snap_kernel": "k1",
|
|
"snap_core": "os1",
|
|
}
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, expected)
|
|
|
|
// do it again, verify its still valid
|
|
err = boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, expected)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20AllSnap(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
// bonus points: we were trying both a base snap and a kernel snap
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
TryBase: s.base2.Filename(),
|
|
BaseStatus: boot.TryingStatus,
|
|
CurrentKernels: []string{s.kern1.Filename(), s.kern2.Filename()},
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
tryKern: s.kern2,
|
|
kernStatus: boot.TryingStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check the bootloader variables
|
|
expected := map[string]string{
|
|
// cleared
|
|
"kernel_status": boot.DefaultStatus,
|
|
}
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, expected)
|
|
|
|
// check that we called EnableKernel() on the try-kernel
|
|
actual, _ := s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableKernel")
|
|
c.Assert(actual, DeepEquals, []snap.PlaceInfo{s.kern2})
|
|
|
|
// and that we disabled a try kernel
|
|
_, nDisableTryCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel")
|
|
c.Assert(nDisableTryCalls, Equals, 1)
|
|
|
|
// also check that the modeenv was updated
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.Base, Equals, s.base2.Filename())
|
|
c.Assert(m2.TryBase, Equals, "")
|
|
c.Assert(m2.BaseStatus, Equals, boot.DefaultStatus)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern2.Filename()})
|
|
|
|
// do it again, verify its still valid
|
|
err = boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, expected)
|
|
|
|
// no new enabled kernels
|
|
actual, _ = s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableKernel")
|
|
c.Assert(actual, DeepEquals, []snap.PlaceInfo{s.kern2})
|
|
// we always disable the try kernel as a cleanup operation, so there's one
|
|
// more call here
|
|
_, nDisableTryCalls = s.bootloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel")
|
|
c.Assert(nDisableTryCalls, Equals, 2)
|
|
}
|
|
|
|
func (s *bootenv20EnvRefKernelSuite) TestMarkBootSuccessful20AllSnap(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
// bonus points: we were trying both a base snap and a kernel snap
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
TryBase: s.base2.Filename(),
|
|
BaseStatus: boot.TryingStatus,
|
|
CurrentKernels: []string{s.kern1.Filename(), s.kern2.Filename()},
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
tryKern: s.kern2,
|
|
kernStatus: boot.TryingStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check the bootloader variables
|
|
expected := map[string]string{
|
|
// cleared
|
|
"kernel_status": boot.DefaultStatus,
|
|
"snap_try_kernel": "",
|
|
// enabled new kernel
|
|
"snap_kernel": s.kern2.Filename(),
|
|
}
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, expected)
|
|
|
|
// also check that the modeenv was updated
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.Base, Equals, s.base2.Filename())
|
|
c.Assert(m2.TryBase, Equals, "")
|
|
c.Assert(m2.BaseStatus, Equals, boot.DefaultStatus)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern2.Filename()})
|
|
|
|
// do it again, verify its still valid
|
|
err = boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, expected)
|
|
}
|
|
|
|
func (s *bootenvSuite) TestMarkBootSuccessfulKernelUpdate(c *C) {
|
|
coreDev := boottest.MockDevice("some-snap")
|
|
|
|
s.bootloader.BootVars["snap_mode"] = boot.TryingStatus
|
|
s.bootloader.BootVars["snap_core"] = "os1"
|
|
s.bootloader.BootVars["snap_kernel"] = "k1"
|
|
s.bootloader.BootVars["snap_try_core"] = ""
|
|
s.bootloader.BootVars["snap_try_kernel"] = "k2"
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, map[string]string{
|
|
// cleared
|
|
"snap_mode": boot.DefaultStatus,
|
|
"snap_try_kernel": "",
|
|
"snap_try_core": "",
|
|
// unchanged
|
|
"snap_core": "os1",
|
|
// updated
|
|
"snap_kernel": "k2",
|
|
})
|
|
}
|
|
|
|
func (s *bootenvSuite) TestMarkBootSuccessfulBaseUpdate(c *C) {
|
|
coreDev := boottest.MockDevice("some-snap")
|
|
|
|
s.bootloader.BootVars["snap_mode"] = boot.TryingStatus
|
|
s.bootloader.BootVars["snap_core"] = "os1"
|
|
s.bootloader.BootVars["snap_kernel"] = "k1"
|
|
s.bootloader.BootVars["snap_try_core"] = "os2"
|
|
s.bootloader.BootVars["snap_try_kernel"] = ""
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, map[string]string{
|
|
// cleared
|
|
"snap_mode": boot.DefaultStatus,
|
|
"snap_try_core": "",
|
|
// unchanged
|
|
"snap_kernel": "k1",
|
|
"snap_try_kernel": "",
|
|
// updated
|
|
"snap_core": "os2",
|
|
})
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20KernelUpdate(c *C) {
|
|
// trying a kernel snap
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename(), s.kern2.Filename()},
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
tryKern: s.kern2,
|
|
kernStatus: boot.TryingStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check the bootloader variables
|
|
expected := map[string]string{"kernel_status": boot.DefaultStatus}
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, expected)
|
|
|
|
// check that MarkBootSuccessful enabled the try kernel
|
|
actual, _ := s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableKernel")
|
|
c.Assert(actual, DeepEquals, []snap.PlaceInfo{s.kern2})
|
|
|
|
// and that we disabled a try kernel
|
|
_, nDisableTryCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel")
|
|
c.Assert(nDisableTryCalls, Equals, 1)
|
|
|
|
// check that the new kernel is the only one in modeenv
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern2.Filename()})
|
|
|
|
// do it again, verify its still valid
|
|
err = boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, expected)
|
|
|
|
// no new bootloader calls
|
|
actual, _ = s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableKernel")
|
|
c.Assert(actual, DeepEquals, []snap.PlaceInfo{s.kern2})
|
|
|
|
// we did disable the kernel again because we always do this to cleanup in
|
|
// case there were leftovers
|
|
_, nDisableTryCalls = s.bootloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel")
|
|
c.Assert(nDisableTryCalls, Equals, 2)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20KernelUpdateWithReseal(c *C) {
|
|
// checked by resealKeyToModeenv
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
|
|
data := []byte("foobar")
|
|
// SHA3-384
|
|
dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
|
|
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
|
|
|
|
// mock the files in cache
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"asset-" + dataHash,
|
|
})
|
|
|
|
assetBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)), bootloader.RoleRunMode)
|
|
newRunKernelBf := bootloader.NewBootFile(filepath.Join(s.kern2.Filename()), "kernel.efi", bootloader.RoleRunMode)
|
|
|
|
tab.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
|
|
newRunKernelBf,
|
|
}
|
|
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
model := coreDev.Model()
|
|
|
|
// trying a kernel snap
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename(), s.kern2.Filename()},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
},
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
tryKern: s.kern2,
|
|
kernStatus: boot.TryingStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// write boot-chains that describe a state in which we have a new kernel
|
|
// candidate (pc-kernel_2)
|
|
bootChains := []boot.BootChain{{
|
|
BrandID: "my-brand",
|
|
Model: "my-model-uc20",
|
|
Grade: "dangerous",
|
|
ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
|
|
AssetChain: []boot.BootAsset{{
|
|
Role: bootloader.RoleRunMode, Name: "asset", Hashes: []string{
|
|
"0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8",
|
|
},
|
|
}},
|
|
Kernel: "pc-kernel",
|
|
KernelRevision: "1",
|
|
KernelCmdlines: []string{"snapd_recovery_mode=run"},
|
|
}, {
|
|
BrandID: "my-brand",
|
|
Model: "my-model-uc20",
|
|
Grade: "dangerous",
|
|
ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
|
|
AssetChain: []boot.BootAsset{{
|
|
Role: bootloader.RoleRunMode, Name: "asset", Hashes: []string{
|
|
"0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8",
|
|
},
|
|
}},
|
|
Kernel: "pc-kernel",
|
|
KernelRevision: "2",
|
|
KernelCmdlines: []string{"snapd_recovery_mode=run"},
|
|
}}
|
|
|
|
err := boot.WriteBootChains(boot.ToPredictableBootChains(bootChains), filepath.Join(dirs.SnapFDEDir, "boot-chains"), 0)
|
|
c.Assert(err, IsNil)
|
|
|
|
resealCalls := 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
|
|
c.Assert(params.ModelParams, HasLen, 1)
|
|
mp := params.ModelParams[0]
|
|
c.Check(mp.Model.Model(), Equals, model.Model())
|
|
for _, ch := range mp.EFILoadChains {
|
|
printChain(c, ch, "-")
|
|
}
|
|
c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{
|
|
secboot.NewLoadChain(assetBf,
|
|
secboot.NewLoadChain(newRunKernelBf)),
|
|
})
|
|
return nil
|
|
})
|
|
defer restore()
|
|
|
|
// mark successful
|
|
err = boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Check(resealCalls, Equals, 1)
|
|
// check the bootloader variables
|
|
expected := map[string]string{
|
|
"kernel_status": boot.DefaultStatus,
|
|
"snap_kernel": s.kern2.Filename(),
|
|
"snap_try_kernel": boot.DefaultStatus,
|
|
}
|
|
c.Assert(tab.BootVars, DeepEquals, expected)
|
|
c.Check(tab.BootChainKernelPath, DeepEquals, []string{s.kern2.MountFile()})
|
|
|
|
// check that the new kernel is the only one in modeenv
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern2.Filename()})
|
|
}
|
|
|
|
func (s *bootenv20EnvRefKernelSuite) TestMarkBootSuccessful20KernelUpdate(c *C) {
|
|
// trying a kernel snap
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename(), s.kern2.Filename()},
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
tryKern: s.kern2,
|
|
kernStatus: boot.TryingStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check the bootloader variables
|
|
expected := map[string]string{
|
|
"kernel_status": boot.DefaultStatus,
|
|
"snap_kernel": s.kern2.Filename(),
|
|
"snap_try_kernel": "",
|
|
}
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, expected)
|
|
|
|
// check that the new kernel is the only one in modeenv
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern2.Filename()})
|
|
|
|
// do it again, verify its still valid
|
|
err = boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, expected)
|
|
c.Assert(s.bootloader.BootVars, DeepEquals, expected)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20BaseUpdate(c *C) {
|
|
// we were trying a base snap
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
TryBase: s.base2.Filename(),
|
|
BaseStatus: boot.TryingStatus,
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check the modeenv
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.Base, Equals, s.base2.Filename())
|
|
c.Assert(m2.TryBase, Equals, "")
|
|
c.Assert(m2.BaseStatus, Equals, "")
|
|
|
|
// do it again, verify its still valid
|
|
err = boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check the modeenv again
|
|
m3, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m3.Base, Equals, s.base2.Filename())
|
|
c.Assert(m3.TryBase, Equals, "")
|
|
c.Assert(m3.BaseStatus, Equals, "")
|
|
}
|
|
|
|
func (s *bootenv20Suite) bootloaderWithTrustedAssets(c *C, trustedAssets map[string]string) *bootloadertest.MockTrustedAssetsBootloader {
|
|
// TODO:UC20: this should be an ExtractedRecoveryKernelImageBootloader
|
|
// because that would reflect our main currently supported
|
|
// trusted assets bootloader (grub)
|
|
tab := bootloadertest.Mock("trusted", "").WithTrustedAssets()
|
|
bootloader.Force(tab)
|
|
tab.TrustedAssetsMap = trustedAssets
|
|
s.AddCleanup(func() { bootloader.Force(nil) })
|
|
return tab
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20BootAssetsUpdateHappy(c *C) {
|
|
// checked by resealKeyToModeenv
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
"shim": "shim",
|
|
})
|
|
|
|
data := []byte("foobar")
|
|
// SHA3-384
|
|
dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
|
|
shim := []byte("shim")
|
|
shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b"
|
|
|
|
c.Assert(os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755), IsNil)
|
|
c.Assert(os.MkdirAll(boot.InitramfsUbuntuSeedDir, 0755), IsNil)
|
|
// only asset for ubuntu
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
|
|
// shim and asset for seed
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "shim"), shim, 0644), IsNil)
|
|
|
|
// mock the files in cache
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"shim-recoveryshimhash",
|
|
"shim-" + shimHash,
|
|
"asset-assethash",
|
|
"asset-recoveryassethash",
|
|
"asset-" + dataHash,
|
|
})
|
|
|
|
shimBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("shim-%s", shimHash)), bootloader.RoleRecovery)
|
|
assetBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)), bootloader.RoleRecovery)
|
|
runKernelBf := bootloader.NewBootFile(filepath.Join(s.kern1.Filename()), "kernel.efi", bootloader.RoleRunMode)
|
|
recoveryKernelBf := bootloader.NewBootFile("pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
|
|
|
|
tab.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "shim", bootloader.RoleRecovery),
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRecovery),
|
|
runKernelBf,
|
|
}
|
|
tab.RecoveryBootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "shim", bootloader.RoleRecovery),
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRecovery),
|
|
recoveryKernelBf,
|
|
}
|
|
|
|
uc20Model := boottest.MakeMockUC20Model()
|
|
|
|
restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
|
|
return uc20Model, []*seed.Snap{mockKernelSeedSnap(snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil
|
|
})
|
|
defer restore()
|
|
|
|
// we were trying an update of boot assets
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"assethash", dataHash},
|
|
},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"recoveryassethash", dataHash},
|
|
"shim": []string{"recoveryshimhash", shimHash},
|
|
},
|
|
CurrentRecoverySystems: []string{"system"},
|
|
|
|
Model: uc20Model.Model(),
|
|
BrandID: uc20Model.BrandID(),
|
|
Grade: string(uc20Model.Grade()),
|
|
ModelSignKeyID: uc20Model.SignKeyID(),
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
coreDev := boottest.MockUC20Device("", uc20Model)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
resealCalls := 0
|
|
restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
|
|
c.Assert(params.ModelParams, HasLen, 1)
|
|
mp := params.ModelParams[0]
|
|
c.Check(mp.Model.Model(), Equals, uc20Model.Model())
|
|
for _, ch := range mp.EFILoadChains {
|
|
printChain(c, ch, "-")
|
|
}
|
|
switch resealCalls {
|
|
case 1:
|
|
c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{
|
|
secboot.NewLoadChain(shimBf,
|
|
secboot.NewLoadChain(assetBf,
|
|
secboot.NewLoadChain(runKernelBf))),
|
|
secboot.NewLoadChain(shimBf,
|
|
secboot.NewLoadChain(assetBf,
|
|
secboot.NewLoadChain(recoveryKernelBf))),
|
|
})
|
|
case 2:
|
|
c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{
|
|
secboot.NewLoadChain(shimBf,
|
|
secboot.NewLoadChain(assetBf,
|
|
secboot.NewLoadChain(recoveryKernelBf))),
|
|
})
|
|
default:
|
|
c.Errorf("unexpected additional call to secboot.ResealKey (call # %d)", resealCalls)
|
|
}
|
|
return nil
|
|
})
|
|
defer restore()
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check the modeenv
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// update assets are in the list
|
|
c.Check(m2.CurrentTrustedBootAssets, DeepEquals, boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
})
|
|
c.Check(m2.CurrentTrustedRecoveryBootAssets, DeepEquals, boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
"shim": []string{shimHash},
|
|
})
|
|
// unused files were dropped from cache
|
|
checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
|
|
filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-"+dataHash),
|
|
filepath.Join(dirs.SnapBootAssetsDir, "trusted", "shim-"+shimHash),
|
|
})
|
|
c.Check(resealCalls, Equals, 2)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20BootAssetsStableStateHappy(c *C) {
|
|
// checked by resealKeyToModeenv
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"nested/asset": "asset",
|
|
"shim": "shim",
|
|
})
|
|
|
|
data := []byte("foobar")
|
|
// SHA3-384
|
|
dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
|
|
shim := []byte("shim")
|
|
shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b"
|
|
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "nested"), 0755), IsNil)
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "nested"), 0755), IsNil)
|
|
// only asset for ubuntu-boot
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "nested/asset"), data, 0644), IsNil)
|
|
// shim and asset for ubuntu-seed
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "nested/asset"), data, 0644), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "shim"), shim, 0644), IsNil)
|
|
|
|
// mock the files in cache
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"shim-" + shimHash,
|
|
"asset-" + dataHash,
|
|
})
|
|
|
|
runKernelBf := bootloader.NewBootFile(filepath.Join(s.kern1.Filename()), "kernel.efi", bootloader.RoleRunMode)
|
|
recoveryKernelBf := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
|
|
|
|
tab.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "shim", bootloader.RoleRecovery),
|
|
bootloader.NewBootFile("", "nested/asset", bootloader.RoleRecovery),
|
|
runKernelBf,
|
|
}
|
|
tab.RecoveryBootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "shim", bootloader.RoleRecovery),
|
|
bootloader.NewBootFile("", "nested/asset", bootloader.RoleRecovery),
|
|
recoveryKernelBf,
|
|
}
|
|
|
|
uc20Model := boottest.MakeMockUC20Model()
|
|
|
|
restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
|
|
return uc20Model, []*seed.Snap{mockNamedKernelSeedSnap(snap.R(1), "pc-kernel-recovery"), mockGadgetSeedSnap(c, nil)}, nil
|
|
})
|
|
defer restore()
|
|
|
|
// we were trying an update of boot assets
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
"shim": []string{shimHash},
|
|
},
|
|
CurrentRecoverySystems: []string{"system"},
|
|
CurrentKernelCommandLines: boot.BootCommandLines{"snapd_recovery_mode=run"},
|
|
|
|
Model: uc20Model.Model(),
|
|
BrandID: uc20Model.BrandID(),
|
|
Grade: string(uc20Model.Grade()),
|
|
ModelSignKeyID: uc20Model.SignKeyID(),
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
coreDev := boottest.MockUC20Device("", uc20Model)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
resealCalls := 0
|
|
restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
return nil
|
|
})
|
|
defer restore()
|
|
|
|
// write boot-chains for current state that will stay unchanged
|
|
bootChains := []boot.BootChain{{
|
|
BrandID: "my-brand",
|
|
Model: "my-model-uc20",
|
|
Grade: "dangerous",
|
|
ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
|
|
AssetChain: []boot.BootAsset{{
|
|
Role: bootloader.RoleRecovery, Name: "shim",
|
|
Hashes: []string{
|
|
"dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b",
|
|
},
|
|
}, {
|
|
Role: bootloader.RoleRecovery, Name: "asset", Hashes: []string{
|
|
"0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8",
|
|
},
|
|
}},
|
|
Kernel: "pc-kernel",
|
|
KernelRevision: "1",
|
|
KernelCmdlines: []string{"snapd_recovery_mode=run"},
|
|
}, {
|
|
BrandID: "my-brand",
|
|
Model: "my-model-uc20",
|
|
Grade: "dangerous",
|
|
ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
|
|
AssetChain: []boot.BootAsset{{
|
|
Role: bootloader.RoleRecovery, Name: "shim",
|
|
Hashes: []string{
|
|
"dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b",
|
|
},
|
|
}, {
|
|
Role: bootloader.RoleRecovery, Name: "asset", Hashes: []string{
|
|
"0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8",
|
|
},
|
|
}},
|
|
Kernel: "pc-kernel-recovery",
|
|
KernelRevision: "1",
|
|
KernelCmdlines: []string{
|
|
"snapd_recovery_mode=factory-reset snapd_recovery_system=system",
|
|
"snapd_recovery_mode=recover snapd_recovery_system=system",
|
|
},
|
|
}}
|
|
|
|
recoveryBootChains := []boot.BootChain{bootChains[1]}
|
|
|
|
err := boot.WriteBootChains(boot.ToPredictableBootChains(bootChains), filepath.Join(dirs.SnapFDEDir, "boot-chains"), 0)
|
|
c.Assert(err, IsNil)
|
|
|
|
err = boot.WriteBootChains(boot.ToPredictableBootChains(recoveryBootChains), filepath.Join(dirs.SnapFDEDir, "recovery-boot-chains"), 0)
|
|
c.Assert(err, IsNil)
|
|
|
|
// mark successful
|
|
err = boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// modeenv is unchanged
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(m2.CurrentTrustedBootAssets, DeepEquals, m.CurrentTrustedBootAssets)
|
|
c.Check(m2.CurrentTrustedRecoveryBootAssets, DeepEquals, m.CurrentTrustedRecoveryBootAssets)
|
|
// files are still in cache
|
|
checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
|
|
filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-"+dataHash),
|
|
filepath.Join(dirs.SnapBootAssetsDir, "trusted", "shim-"+shimHash),
|
|
})
|
|
|
|
// boot chains were built
|
|
c.Check(tab.BootChainKernelPath, DeepEquals, []string{
|
|
s.kern1.MountFile(),
|
|
})
|
|
// no actual reseal
|
|
c.Check(resealCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20BootUnassertedKernelAssetsStableStateHappy(c *C) {
|
|
// checked by resealKeyToModeenv
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"nested/asset": "asset",
|
|
"shim": "shim",
|
|
})
|
|
|
|
data := []byte("foobar")
|
|
// SHA3-384
|
|
dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
|
|
shim := []byte("shim")
|
|
shimHash := "dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b"
|
|
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "nested"), 0755), IsNil)
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "nested"), 0755), IsNil)
|
|
// only asset for ubuntu-boot
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "nested/asset"), data, 0644), IsNil)
|
|
// shim and asset for ubuntu-seed
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "nested/asset"), data, 0644), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "shim"), shim, 0644), IsNil)
|
|
|
|
// mock the files in cache
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"shim-" + shimHash,
|
|
"asset-" + dataHash,
|
|
})
|
|
|
|
runKernelBf := bootloader.NewBootFile(filepath.Join(s.ukern1.Filename()), "kernel.efi", bootloader.RoleRunMode)
|
|
recoveryKernelBf := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
|
|
|
|
tab.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "shim", bootloader.RoleRecovery),
|
|
bootloader.NewBootFile("", "nested/asset", bootloader.RoleRecovery),
|
|
runKernelBf,
|
|
}
|
|
tab.RecoveryBootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "shim", bootloader.RoleRecovery),
|
|
bootloader.NewBootFile("", "nested/asset", bootloader.RoleRecovery),
|
|
recoveryKernelBf,
|
|
}
|
|
|
|
uc20Model := boottest.MakeMockUC20Model()
|
|
|
|
restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
|
|
return uc20Model, []*seed.Snap{mockNamedKernelSeedSnap(snap.R(1), "pc-kernel-recovery"), mockGadgetSeedSnap(c, nil)}, nil
|
|
})
|
|
defer restore()
|
|
|
|
// we were trying an update of boot assets
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.ukern1.Filename()},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
"shim": []string{shimHash},
|
|
},
|
|
CurrentRecoverySystems: []string{"system"},
|
|
GoodRecoverySystems: []string{"system"},
|
|
CurrentKernelCommandLines: boot.BootCommandLines{"snapd_recovery_mode=run"},
|
|
// leave this comment to keep old gofmt happy
|
|
Model: "my-model-uc20",
|
|
BrandID: "my-brand",
|
|
Grade: "dangerous",
|
|
ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.ukern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
coreDev := boottest.MockUC20Device("", uc20Model)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
resealCalls := 0
|
|
restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
return nil
|
|
})
|
|
defer restore()
|
|
|
|
// write boot-chains for current state that will stay unchanged
|
|
bootChains := []boot.BootChain{{
|
|
BrandID: "my-brand",
|
|
Model: "my-model-uc20",
|
|
Grade: "dangerous",
|
|
ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
|
|
AssetChain: []boot.BootAsset{{
|
|
Role: bootloader.RoleRecovery, Name: "shim",
|
|
Hashes: []string{
|
|
"dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b",
|
|
},
|
|
}, {
|
|
Role: bootloader.RoleRecovery, Name: "asset", Hashes: []string{
|
|
"0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8",
|
|
},
|
|
}},
|
|
Kernel: "pc-kernel",
|
|
// unasserted kernel snap
|
|
KernelRevision: "",
|
|
KernelCmdlines: []string{"snapd_recovery_mode=run"},
|
|
}, {
|
|
BrandID: "my-brand",
|
|
Model: "my-model-uc20",
|
|
Grade: "dangerous",
|
|
ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
|
|
AssetChain: []boot.BootAsset{{
|
|
Role: bootloader.RoleRecovery, Name: "shim",
|
|
Hashes: []string{
|
|
"dac0063e831d4b2e7a330426720512fc50fa315042f0bb30f9d1db73e4898dcb89119cac41fdfa62137c8931a50f9d7b",
|
|
},
|
|
}, {
|
|
Role: bootloader.RoleRecovery, Name: "asset", Hashes: []string{
|
|
"0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8",
|
|
},
|
|
}},
|
|
Kernel: "pc-kernel-recovery",
|
|
KernelRevision: "1",
|
|
KernelCmdlines: []string{
|
|
"snapd_recovery_mode=factory-reset snapd_recovery_system=system",
|
|
"snapd_recovery_mode=recover snapd_recovery_system=system",
|
|
},
|
|
}}
|
|
|
|
recoveryBootChains := []boot.BootChain{bootChains[1]}
|
|
|
|
err := boot.WriteBootChains(boot.ToPredictableBootChains(bootChains), filepath.Join(dirs.SnapFDEDir, "boot-chains"), 0)
|
|
c.Assert(err, IsNil)
|
|
|
|
err = boot.WriteBootChains(boot.ToPredictableBootChains(recoveryBootChains), filepath.Join(dirs.SnapFDEDir, "recovery-boot-chains"), 0)
|
|
c.Assert(err, IsNil)
|
|
|
|
// mark successful
|
|
err = boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// modeenv is unchanged
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(m2.CurrentTrustedBootAssets, DeepEquals, m.CurrentTrustedBootAssets)
|
|
c.Check(m2.CurrentTrustedRecoveryBootAssets, DeepEquals, m.CurrentTrustedRecoveryBootAssets)
|
|
// files are still in cache
|
|
checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
|
|
filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-"+dataHash),
|
|
filepath.Join(dirs.SnapBootAssetsDir, "trusted", "shim-"+shimHash),
|
|
})
|
|
|
|
// boot chains were built
|
|
c.Check(tab.BootChainKernelPath, DeepEquals, []string{
|
|
s.ukern1.MountFile(),
|
|
})
|
|
// no actual reseal
|
|
c.Check(resealCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20BootAssetsUpdateUnexpectedAsset(c *C) {
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"EFI/asset": "efi:asset",
|
|
})
|
|
|
|
data := []byte("foobar")
|
|
// SHA3-384
|
|
dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
|
|
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "EFI"), 0755), IsNil)
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI"), 0755), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "EFI/asset"), data, 0644), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI/asset"), data, 0644), IsNil)
|
|
// mock some state in the cache
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"asset-one",
|
|
"asset-two",
|
|
})
|
|
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
model := coreDev.Model()
|
|
|
|
// we were trying an update of boot assets
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
// hash will not match
|
|
"asset": []string{"one", "two"},
|
|
},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"one", "two"},
|
|
},
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot mark boot successful: cannot mark successful boot assets: system booted with unexpected run mode bootloader asset "EFI/asset" hash %s`, dataHash))
|
|
|
|
// check the modeenv
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// modeenv is unchaged
|
|
c.Check(m2.CurrentTrustedBootAssets, DeepEquals, m.CurrentTrustedBootAssets)
|
|
c.Check(m2.CurrentTrustedRecoveryBootAssets, DeepEquals, m.CurrentTrustedRecoveryBootAssets)
|
|
// nothing was removed from cache
|
|
checkContentGlob(c, filepath.Join(dirs.SnapBootAssetsDir, "trusted", "*"), []string{
|
|
filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-one"),
|
|
filepath.Join(dirs.SnapBootAssetsDir, "trusted", "asset-two"),
|
|
})
|
|
}
|
|
|
|
func (s *bootenv20Suite) setupMarkBootSuccessful20CommandLine(c *C, model *asserts.Model, mode string, cmdlines boot.BootCommandLines) *boot.Modeenv {
|
|
// mock some state in the cache
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"asset-one",
|
|
})
|
|
// a pending kernel command line change
|
|
m := &boot.Modeenv{
|
|
Mode: mode,
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"one"},
|
|
},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"one"},
|
|
},
|
|
CurrentKernelCommandLines: cmdlines,
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
return m
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20CommandLineUpdatedHappy(c *C) {
|
|
s.mockCmdline(c, "snapd_recovery_mode=run candidate panic=-1")
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
m := s.setupMarkBootSuccessful20CommandLine(c, coreDev.Model(), "run", boot.BootCommandLines{
|
|
"snapd_recovery_mode=run panic=-1",
|
|
"snapd_recovery_mode=run candidate panic=-1",
|
|
})
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check the modeenv
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// modeenv is unchaged
|
|
c.Check(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run candidate panic=-1",
|
|
})
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20CommandLineUpdatedOld(c *C) {
|
|
s.mockCmdline(c, "snapd_recovery_mode=run panic=-1")
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
m := s.setupMarkBootSuccessful20CommandLine(c, coreDev.Model(), "run", boot.BootCommandLines{
|
|
"snapd_recovery_mode=run panic=-1",
|
|
"snapd_recovery_mode=run candidate panic=-1",
|
|
})
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check the modeenv
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// modeenv is unchaged
|
|
c.Check(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run panic=-1",
|
|
})
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20CommandLineUpdatedMismatch(c *C) {
|
|
s.mockCmdline(c, "snapd_recovery_mode=run different")
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
m := s.setupMarkBootSuccessful20CommandLine(c, coreDev.Model(), "run", boot.BootCommandLines{
|
|
"snapd_recovery_mode=run",
|
|
"snapd_recovery_mode=run candidate",
|
|
})
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, ErrorMatches, `cannot mark boot successful: cannot mark successful boot command line: current command line content "snapd_recovery_mode=run different" not matching any expected entry`)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20CommandLineUpdatedFallbackOnBootSuccessful(c *C) {
|
|
s.mockCmdline(c, "snapd_recovery_mode=run panic=-1")
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
tab.StaticCommandLine = "panic=-1"
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
m := s.setupMarkBootSuccessful20CommandLine(c, coreDev.Model(), "run", nil)
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check the modeenv
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// modeenv is unchaged
|
|
c.Check(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run panic=-1",
|
|
})
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20CommandLineUpdatedFallbackOnBootMismatch(c *C) {
|
|
s.mockCmdline(c, "snapd_recovery_mode=run panic=-1 unexpected")
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
tab.StaticCommandLine = "panic=-1"
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
m := s.setupMarkBootSuccessful20CommandLine(c, coreDev.Model(), "run", nil)
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, ErrorMatches, `cannot mark boot successful: cannot mark successful boot command line: unexpected current command line: "snapd_recovery_mode=run panic=-1 unexpected"`)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20CommandLineNonRunMode(c *C) {
|
|
// recover mode
|
|
s.mockCmdline(c, "snapd_recovery_mode=recover snapd_recovery_system=1234 panic=-1")
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
tab.StaticCommandLine = "panic=-1"
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
// current command line does not match any of the run mode command lines
|
|
m := s.setupMarkBootSuccessful20CommandLine(c, coreDev.Model(), "recover", boot.BootCommandLines{
|
|
"snapd_recovery_mode=run panic=-1",
|
|
"snapd_recovery_mode=run candidate panic=-1",
|
|
})
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check the modeenv
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// modeenv is unchaged
|
|
c.Check(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run panic=-1",
|
|
"snapd_recovery_mode=run candidate panic=-1",
|
|
})
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20CommandLineUpdatedNoFDEManagedBootloader(c *C) {
|
|
s.mockCmdline(c, "snapd_recovery_mode=run candidate panic=-1")
|
|
tab := s.bootloaderWithTrustedAssets(c, nil)
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
m := s.setupMarkBootSuccessful20CommandLine(c, coreDev.Model(), "run", boot.BootCommandLines{
|
|
"snapd_recovery_mode=run panic=-1",
|
|
"snapd_recovery_mode=run candidate panic=-1",
|
|
})
|
|
// without encryption, the trusted assets are not tracked in the modeenv,
|
|
// but we still may want to track command lines so that the gadget can
|
|
// contribute to the system command line
|
|
m.CurrentTrustedBootAssets = nil
|
|
m.CurrentTrustedRecoveryBootAssets = nil
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check the modeenv
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// modeenv is unchaged
|
|
c.Check(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run candidate panic=-1",
|
|
})
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20CommandLineCompatNonTrustedBootloader(c *C) {
|
|
s.mockCmdline(c, "snapd_recovery_mode=run candidate panic=-1")
|
|
// bootloader has no trusted assets
|
|
bl := bootloadertest.Mock("not-trusted", "")
|
|
bootloader.Force(bl)
|
|
s.AddCleanup(func() { bootloader.Force(nil) })
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
m := s.setupMarkBootSuccessful20CommandLine(c, coreDev.Model(), "run", nil)
|
|
// no trusted assets
|
|
m.CurrentTrustedBootAssets = nil
|
|
m.CurrentTrustedRecoveryBootAssets = nil
|
|
// no kernel command lines tracked
|
|
m.CurrentKernelCommandLines = nil
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
bl,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check the modeenv
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// modeenv isn't changed
|
|
c.Check(m2.CurrentKernelCommandLines, HasLen, 0)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20SystemsCompat(c *C) {
|
|
b := bootloadertest.Mock("mock", s.bootdir)
|
|
s.forceBootloader(b)
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
CurrentRecoverySystems: []string{"1234"},
|
|
}
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
b,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check the modeenv
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// the list of good recovery systems has not been modified
|
|
c.Check(m2.GoodRecoverySystems, DeepEquals, []string{"1234"})
|
|
c.Check(m2.CurrentRecoverySystems, DeepEquals, []string{"1234"})
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20SystemsPopulated(c *C) {
|
|
b := bootloadertest.Mock("mock", s.bootdir)
|
|
s.forceBootloader(b)
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
CurrentRecoverySystems: []string{"1234", "9999"},
|
|
GoodRecoverySystems: []string{"1234"},
|
|
}
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
b,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check the modeenv
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// good recovery systems has been populated
|
|
c.Check(m2.GoodRecoverySystems, DeepEquals, []string{"1234"})
|
|
c.Check(m2.CurrentRecoverySystems, DeepEquals, []string{"1234", "9999"})
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessful20ModelSignKeyIDPopulated(c *C) {
|
|
b := bootloadertest.Mock("mock", s.bootdir)
|
|
s.forceBootloader(b)
|
|
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
Model: "my-model-uc20",
|
|
BrandID: "my-brand",
|
|
Grade: "dangerous",
|
|
// sign key ID is unset
|
|
}
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
b,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(coreDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// check the modeenv
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// model's sign key ID has been set
|
|
c.Check(m2.ModelSignKeyID, Equals, "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij")
|
|
c.Check(m2.Model, Equals, "my-model-uc20")
|
|
c.Check(m2.BrandID, Equals, "my-brand")
|
|
c.Check(m2.Grade, Equals, "dangerous")
|
|
}
|
|
|
|
type recoveryBootenv20Suite struct {
|
|
baseBootenvSuite
|
|
|
|
bootloader *bootloadertest.MockBootloader
|
|
|
|
dev snap.Device
|
|
}
|
|
|
|
var _ = Suite(&recoveryBootenv20Suite{})
|
|
|
|
func (s *recoveryBootenv20Suite) SetUpTest(c *C) {
|
|
s.baseBootenvSuite.SetUpTest(c)
|
|
|
|
s.bootloader = bootloadertest.Mock("mock", c.MkDir())
|
|
s.forceBootloader(s.bootloader)
|
|
|
|
s.dev = boottest.MockUC20Device("", nil)
|
|
}
|
|
|
|
func (s *recoveryBootenv20Suite) TestSetRecoveryBootSystemAndModeHappy(c *C) {
|
|
err := boot.SetRecoveryBootSystemAndMode(s.dev, "1234", "install")
|
|
c.Assert(err, IsNil)
|
|
c.Check(s.bootloader.BootVars, DeepEquals, map[string]string{
|
|
"snapd_recovery_system": "1234",
|
|
"snapd_recovery_mode": "install",
|
|
})
|
|
}
|
|
|
|
func (s *recoveryBootenv20Suite) TestSetRecoveryBootSystemAndModeSetErr(c *C) {
|
|
s.bootloader.SetErr = errors.New("no can do")
|
|
err := boot.SetRecoveryBootSystemAndMode(s.dev, "1234", "install")
|
|
c.Assert(err, ErrorMatches, `no can do`)
|
|
}
|
|
|
|
func (s *recoveryBootenv20Suite) TestSetRecoveryBootSystemAndModeNonUC20(c *C) {
|
|
non20Dev := boottest.MockDevice("some-snap")
|
|
err := boot.SetRecoveryBootSystemAndMode(non20Dev, "1234", "install")
|
|
c.Assert(err, Equals, boot.ErrUnsupportedSystemMode)
|
|
}
|
|
|
|
func (s *recoveryBootenv20Suite) TestSetRecoveryBootSystemAndModeErrClumsy(c *C) {
|
|
err := boot.SetRecoveryBootSystemAndMode(s.dev, "", "install")
|
|
c.Assert(err, ErrorMatches, "internal error: system label is unset")
|
|
err = boot.SetRecoveryBootSystemAndMode(s.dev, "1234", "")
|
|
c.Assert(err, ErrorMatches, "internal error: system mode is unset")
|
|
}
|
|
|
|
func (s *recoveryBootenv20Suite) TestSetRecoveryBootSystemAndModeRealHappy(c *C) {
|
|
bootloader.Force(nil)
|
|
|
|
mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu")
|
|
err := os.MkdirAll(mockSeedGrubDir, 0755)
|
|
c.Assert(err, IsNil)
|
|
err = os.WriteFile(filepath.Join(mockSeedGrubDir, "grub.cfg"), nil, 0644)
|
|
c.Assert(err, IsNil)
|
|
|
|
err = boot.SetRecoveryBootSystemAndMode(s.dev, "1234", "install")
|
|
c.Assert(err, IsNil)
|
|
|
|
bl, err := bootloader.Find(boot.InitramfsUbuntuSeedDir, &bootloader.Options{Role: bootloader.RoleRecovery})
|
|
c.Assert(err, IsNil)
|
|
|
|
blvars, err := bl.GetBootVars("snapd_recovery_mode", "snapd_recovery_system")
|
|
c.Assert(err, IsNil)
|
|
c.Check(blvars, DeepEquals, map[string]string{
|
|
"snapd_recovery_system": "1234",
|
|
"snapd_recovery_mode": "install",
|
|
})
|
|
}
|
|
|
|
type bootConfigSuite struct {
|
|
baseBootenvSuite
|
|
|
|
bootloader *bootloadertest.MockTrustedAssetsBootloader
|
|
gadgetSnap string
|
|
}
|
|
|
|
var _ = Suite(&bootConfigSuite{})
|
|
|
|
func (s *bootConfigSuite) SetUpTest(c *C) {
|
|
s.baseBootenvSuite.SetUpTest(c)
|
|
|
|
s.bootloader = bootloadertest.Mock("trusted", c.MkDir()).WithTrustedAssets()
|
|
s.bootloader.StaticCommandLine = "this is mocked panic=-1"
|
|
s.bootloader.CandidateStaticCommandLine = "mocked candidate panic=-1"
|
|
s.forceBootloader(s.bootloader)
|
|
|
|
mockGadgetYaml := `
|
|
volumes:
|
|
volumename:
|
|
bootloader: grub
|
|
`
|
|
|
|
s.mockCmdline(c, "snapd_recovery_mode=run this is mocked panic=-1")
|
|
s.gadgetSnap = snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{{"meta/gadget.yaml", mockGadgetYaml}})
|
|
}
|
|
|
|
func (s *bootConfigSuite) mockCmdline(c *C, cmdline string) {
|
|
c.Assert(os.WriteFile(s.cmdlineFile, []byte(cmdline), 0644), IsNil)
|
|
}
|
|
|
|
func (s *bootConfigSuite) TestBootConfigUpdateHappyNoKeysNoReseal(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
CurrentKernelCommandLines: boot.BootCommandLines{
|
|
"snapd_recovery_mode=run this is mocked panic=-1",
|
|
},
|
|
}
|
|
c.Assert(m.WriteTo(""), IsNil)
|
|
|
|
resealCalls := 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
return nil
|
|
})
|
|
defer restore()
|
|
|
|
updated, err := boot.UpdateManagedBootConfigs(coreDev, s.gadgetSnap, "")
|
|
c.Assert(err, IsNil)
|
|
c.Check(updated, Equals, true)
|
|
c.Check(s.bootloader.UpdateCalls, Equals, 1)
|
|
c.Check(resealCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootConfigSuite) testBootConfigUpdateHappyWithReseal(c *C, cmdlineAppend string) {
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
runKernelBf := bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_600.snap", "kernel.efi", bootloader.RoleRunMode)
|
|
recoveryKernelBf := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"asset-hash-1",
|
|
})
|
|
|
|
s.bootloader.TrustedAssetsMap = map[string]string{"asset": "asset"}
|
|
s.bootloader.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
|
|
runKernelBf,
|
|
}
|
|
s.bootloader.RecoveryBootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRecovery),
|
|
recoveryKernelBf,
|
|
}
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
CurrentKernels: []string{"pc-kernel_500.snap"},
|
|
CurrentKernelCommandLines: boot.BootCommandLines{
|
|
"snapd_recovery_mode=run this is mocked panic=-1",
|
|
},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"hash-1"},
|
|
},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"hash-1"},
|
|
},
|
|
}
|
|
c.Assert(m.WriteTo(""), IsNil)
|
|
|
|
newCmdline := strutil.JoinNonEmpty([]string{
|
|
"snapd_recovery_mode=run mocked candidate panic=-1", cmdlineAppend}, " ")
|
|
resealCalls := 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
c.Assert(params, NotNil)
|
|
c.Assert(params.ModelParams, HasLen, 1)
|
|
c.Check(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
|
|
newCmdline,
|
|
"snapd_recovery_mode=run this is mocked panic=-1",
|
|
})
|
|
return nil
|
|
})
|
|
defer restore()
|
|
|
|
updated, err := boot.UpdateManagedBootConfigs(coreDev, s.gadgetSnap, cmdlineAppend)
|
|
c.Assert(err, IsNil)
|
|
c.Check(updated, Equals, true)
|
|
c.Check(s.bootloader.UpdateCalls, Equals, 1)
|
|
c.Check(resealCalls, Equals, 1)
|
|
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run this is mocked panic=-1",
|
|
newCmdline,
|
|
})
|
|
}
|
|
|
|
func (s *bootConfigSuite) TestBootConfigUpdateHappyWithReseal(c *C) {
|
|
s.testBootConfigUpdateHappyWithReseal(c, "")
|
|
}
|
|
|
|
func (s *bootConfigSuite) TestBootConfigUpdateHappyCmdlineAppendWithReseal(c *C) {
|
|
s.testBootConfigUpdateHappyWithReseal(c, "foo bar")
|
|
}
|
|
|
|
func (s *bootConfigSuite) testBootConfigUpdateHappyNoChange(c *C, cmdlineAppend string) {
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
s.bootloader.StaticCommandLine = "mocked unchanged panic=-1"
|
|
s.bootloader.CandidateStaticCommandLine = "mocked unchanged panic=-1"
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
CurrentKernelCommandLines: boot.BootCommandLines{
|
|
strutil.JoinNonEmpty([]string{
|
|
"snapd_recovery_mode=run mocked unchanged panic=-1", cmdlineAppend}, " "),
|
|
},
|
|
}
|
|
c.Assert(m.WriteTo(""), IsNil)
|
|
|
|
resealCalls := 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
return nil
|
|
})
|
|
defer restore()
|
|
|
|
updated, err := boot.UpdateManagedBootConfigs(coreDev, s.gadgetSnap, cmdlineAppend)
|
|
c.Assert(err, IsNil)
|
|
c.Check(updated, Equals, false)
|
|
c.Check(s.bootloader.UpdateCalls, Equals, 1)
|
|
c.Check(resealCalls, Equals, 0)
|
|
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernelCommandLines, HasLen, 1)
|
|
}
|
|
|
|
func (s *bootConfigSuite) TestBootConfigUpdateHappyNoChange(c *C) {
|
|
s.testBootConfigUpdateHappyNoChange(c, "")
|
|
}
|
|
|
|
func (s *bootConfigSuite) TestBootConfigUpdateHappyCmdlineAppendNoChange(c *C) {
|
|
s.testBootConfigUpdateHappyNoChange(c, "foo bar")
|
|
}
|
|
|
|
func (s *bootConfigSuite) TestBootConfigUpdateNonUC20DoesNothing(c *C) {
|
|
nonUC20coreDev := boottest.MockDevice("pc-kernel")
|
|
c.Assert(nonUC20coreDev.HasModeenv(), Equals, false)
|
|
updated, err := boot.UpdateManagedBootConfigs(nonUC20coreDev, s.gadgetSnap, "")
|
|
c.Assert(err, IsNil)
|
|
c.Check(updated, Equals, false)
|
|
c.Check(s.bootloader.UpdateCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootConfigSuite) TestBootConfigUpdateBadModeErr(c *C) {
|
|
uc20Dev := boottest.MockUC20Device("recover", nil)
|
|
c.Assert(uc20Dev.HasModeenv(), Equals, true)
|
|
updated, err := boot.UpdateManagedBootConfigs(uc20Dev, s.gadgetSnap, "")
|
|
c.Assert(err, ErrorMatches, "internal error: boot config can only be updated in run mode")
|
|
c.Check(updated, Equals, false)
|
|
c.Check(s.bootloader.UpdateCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootConfigSuite) TestBootConfigUpdateFailErr(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
CurrentKernelCommandLines: boot.BootCommandLines{
|
|
"snapd_recovery_mode=run this is mocked panic=-1",
|
|
},
|
|
}
|
|
c.Assert(m.WriteTo(""), IsNil)
|
|
|
|
s.bootloader.UpdateErr = errors.New("update fail")
|
|
|
|
updated, err := boot.UpdateManagedBootConfigs(coreDev, s.gadgetSnap, "")
|
|
c.Assert(err, ErrorMatches, "update fail")
|
|
c.Check(updated, Equals, false)
|
|
c.Check(s.bootloader.UpdateCalls, Equals, 1)
|
|
}
|
|
|
|
func (s *bootConfigSuite) TestBootConfigUpdateCmdlineMismatchErr(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
}
|
|
c.Assert(m.WriteTo(""), IsNil)
|
|
|
|
s.mockCmdline(c, "snapd_recovery_mode=run unexpected cmdline")
|
|
|
|
updated, err := boot.UpdateManagedBootConfigs(coreDev, s.gadgetSnap, "")
|
|
c.Assert(err, ErrorMatches, `internal error: current kernel command lines is unset`)
|
|
c.Check(updated, Equals, false)
|
|
c.Check(s.bootloader.UpdateCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootConfigSuite) TestBootConfigUpdateNotManagedErr(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
bl := bootloadertest.Mock("not-managed", c.MkDir())
|
|
bootloader.Force(bl)
|
|
defer bootloader.Force(nil)
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
}
|
|
c.Assert(m.WriteTo(""), IsNil)
|
|
|
|
updated, err := boot.UpdateManagedBootConfigs(coreDev, s.gadgetSnap, "")
|
|
c.Assert(err, IsNil)
|
|
c.Check(updated, Equals, false)
|
|
c.Check(s.bootloader.UpdateCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootConfigSuite) TestBootConfigUpdateBootloaderFindErr(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
bootloader.ForceError(errors.New("mocked find error"))
|
|
defer bootloader.ForceError(nil)
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
}
|
|
c.Assert(m.WriteTo(""), IsNil)
|
|
|
|
updated, err := boot.UpdateManagedBootConfigs(coreDev, s.gadgetSnap, "")
|
|
c.Assert(err, ErrorMatches, "internal error: cannot find trusted assets bootloader under .*: mocked find error")
|
|
c.Check(updated, Equals, false)
|
|
c.Check(s.bootloader.UpdateCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootConfigSuite) TestBootConfigUpdateWithGadgetAndReseal(c *C) {
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
mockGadgetYaml := `
|
|
volumes:
|
|
volumename:
|
|
bootloader: grub
|
|
`
|
|
gadgetSnap := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{
|
|
{"cmdline.extra", "foo bar baz"},
|
|
{"meta/gadget.yaml", mockGadgetYaml},
|
|
})
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
runKernelBf := bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_600.snap", "kernel.efi", bootloader.RoleRunMode)
|
|
recoveryKernelBf := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"asset-hash-1",
|
|
})
|
|
|
|
s.bootloader.TrustedAssetsMap = map[string]string{"asset": "asset"}
|
|
s.bootloader.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
|
|
runKernelBf,
|
|
}
|
|
s.bootloader.RecoveryBootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRecovery),
|
|
recoveryKernelBf,
|
|
}
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
CurrentKernels: []string{"pc-kernel_500.snap"},
|
|
CurrentKernelCommandLines: boot.BootCommandLines{
|
|
// the extra arguments would be included in the current
|
|
// command line already
|
|
"snapd_recovery_mode=run this is mocked panic=-1 foo bar baz",
|
|
},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"hash-1"},
|
|
},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"hash-1"},
|
|
},
|
|
}
|
|
c.Assert(m.WriteTo(""), IsNil)
|
|
|
|
resealCalls := 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
c.Assert(params, NotNil)
|
|
c.Assert(params.ModelParams, HasLen, 1)
|
|
c.Check(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
|
|
"snapd_recovery_mode=run mocked candidate panic=-1 foo bar baz",
|
|
"snapd_recovery_mode=run this is mocked panic=-1 foo bar baz",
|
|
})
|
|
return nil
|
|
})
|
|
defer restore()
|
|
|
|
updated, err := boot.UpdateManagedBootConfigs(coreDev, gadgetSnap, "")
|
|
c.Assert(err, IsNil)
|
|
c.Check(updated, Equals, true)
|
|
c.Check(s.bootloader.UpdateCalls, Equals, 1)
|
|
c.Check(resealCalls, Equals, 1)
|
|
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run this is mocked panic=-1 foo bar baz",
|
|
"snapd_recovery_mode=run mocked candidate panic=-1 foo bar baz",
|
|
})
|
|
}
|
|
|
|
func (s *bootConfigSuite) TestBootConfigUpdateWithGadgetFullAndReseal(c *C) {
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
mockGadgetYaml := `
|
|
volumes:
|
|
volumename:
|
|
bootloader: grub
|
|
`
|
|
gadgetSnap := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{
|
|
{"cmdline.full", "foo bar baz"},
|
|
{"meta/gadget.yaml", mockGadgetYaml},
|
|
})
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
// a minimal bootloader and modeenv setup that works because reseal is
|
|
// not executed
|
|
s.bootloader.TrustedAssetsMap = map[string]string{"asset": "asset"}
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
CurrentKernelCommandLines: boot.BootCommandLines{
|
|
// the full arguments would be included in the current
|
|
// command line already
|
|
"snapd_recovery_mode=run foo bar baz",
|
|
},
|
|
}
|
|
c.Assert(m.WriteTo(""), IsNil)
|
|
|
|
s.bootloader.Updated = true
|
|
|
|
resealCalls := 0
|
|
// reseal does not happen, because the gadget overrides the static
|
|
// command line which is part of boot config, thus there's no resulting
|
|
// change in the command lines tracked in modeenv and no need to reseal
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
return fmt.Errorf("unexpected call")
|
|
})
|
|
defer restore()
|
|
|
|
updated, err := boot.UpdateManagedBootConfigs(coreDev, gadgetSnap, "")
|
|
c.Assert(err, IsNil)
|
|
c.Check(updated, Equals, true)
|
|
c.Check(s.bootloader.UpdateCalls, Equals, 1)
|
|
c.Check(resealCalls, Equals, 0)
|
|
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run foo bar baz",
|
|
})
|
|
}
|
|
|
|
type bootKernelCommandLineSuite struct {
|
|
baseBootenvSuite
|
|
|
|
bootloader *bootloadertest.MockTrustedAssetsBootloader
|
|
gadgetSnap string
|
|
uc20dev snap.Device
|
|
recoveryKernelBf bootloader.BootFile
|
|
runKernelBf bootloader.BootFile
|
|
modeenvWithEncryption *boot.Modeenv
|
|
resealCalls int
|
|
resealCommandLines [][]string
|
|
}
|
|
|
|
var _ = Suite(&bootKernelCommandLineSuite{})
|
|
|
|
func (s *bootKernelCommandLineSuite) SetUpTest(c *C) {
|
|
s.baseBootenvSuite.SetUpTest(c)
|
|
|
|
data := []byte("foobar")
|
|
// SHA3-384
|
|
dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil)
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir), 0755), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil)
|
|
|
|
s.bootloader = bootloadertest.Mock("trusted", c.MkDir()).WithTrustedAssets()
|
|
s.bootloader.TrustedAssetsMap = map[string]string{"asset": "asset"}
|
|
s.bootloader.StaticCommandLine = "static mocked panic=-1"
|
|
s.bootloader.CandidateStaticCommandLine = "mocked candidate panic=-1"
|
|
s.forceBootloader(s.bootloader)
|
|
|
|
s.mockCmdline(c, "snapd_recovery_mode=run this is mocked panic=-1")
|
|
s.gadgetSnap = snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, nil)
|
|
s.uc20dev = boottest.MockUC20Device("", boottest.MakeMockUC20Model(nil))
|
|
s.runKernelBf = bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_600.snap", "kernel.efi", bootloader.RoleRunMode)
|
|
s.recoveryKernelBf = bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery)
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"asset-" + dataHash,
|
|
})
|
|
|
|
s.bootloader.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
|
|
s.runKernelBf,
|
|
}
|
|
s.bootloader.RecoveryBootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRecovery),
|
|
s.recoveryKernelBf,
|
|
}
|
|
s.modeenvWithEncryption = &boot.Modeenv{
|
|
Mode: "run",
|
|
CurrentKernels: []string{"pc-kernel_500.snap"},
|
|
Base: "core20_1.snap",
|
|
BaseStatus: boot.DefaultStatus,
|
|
CurrentKernelCommandLines: boot.BootCommandLines{
|
|
// the extra arguments would be included in the current
|
|
// command line already
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
},
|
|
}
|
|
s.bootloader.SetBootVars(map[string]string{
|
|
"snap_kernel": "pc-kernel_500.snap",
|
|
})
|
|
s.bootloader.SetBootVarsCalls = 0
|
|
|
|
s.resealCommandLines = nil
|
|
s.resealCalls = 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
s.resealCalls++
|
|
c.Assert(params, NotNil)
|
|
c.Assert(params.ModelParams, HasLen, 1)
|
|
s.resealCommandLines = append(s.resealCommandLines, params.ModelParams[0].KernelCmdlines)
|
|
return nil
|
|
})
|
|
s.AddCleanup(restore)
|
|
}
|
|
|
|
func (s *bootKernelCommandLineSuite) TestCommandLineUpdateNonUC20(c *C) {
|
|
nonUC20dev := boottest.MockDevice("")
|
|
|
|
// gadget which would otherwise trigger an update
|
|
sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{
|
|
{"cmdline.extra", "foo"},
|
|
})
|
|
|
|
reboot, err := boot.UpdateCommandLineForGadgetComponent(nonUC20dev, sf, "")
|
|
c.Assert(err, ErrorMatches, `internal error: command line component cannot be updated on pre-UC20 devices`)
|
|
c.Assert(reboot, Equals, false)
|
|
}
|
|
|
|
func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20NotManagedBootloader(c *C) {
|
|
// gadget which would otherwise trigger an update
|
|
sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{
|
|
{"cmdline.extra", "foo"},
|
|
})
|
|
|
|
// but the bootloader is not managed by snapd
|
|
bl := bootloadertest.Mock("not-managed", c.MkDir())
|
|
bl.SetErr = fmt.Errorf("unexpected call")
|
|
s.forceBootloader(bl)
|
|
|
|
reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf, "")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(reboot, Equals, false)
|
|
c.Check(bl.SetBootVarsCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20ArgsAdded(c *C) {
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
mockGadgetYaml := `
|
|
volumes:
|
|
volumename:
|
|
bootloader: grub
|
|
`
|
|
sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{
|
|
{"cmdline.extra", "args from gadget"},
|
|
{"meta/gadget.yaml", mockGadgetYaml},
|
|
})
|
|
|
|
s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run static mocked panic=-1"}
|
|
c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil)
|
|
|
|
reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf, "")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(reboot, Equals, true)
|
|
|
|
// reseal happened
|
|
c.Check(s.resealCalls, Equals, 1)
|
|
c.Check(s.resealCommandLines, DeepEquals, [][]string{{
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
"snapd_recovery_mode=run static mocked panic=-1 args from gadget",
|
|
}})
|
|
|
|
// modeenv has been updated
|
|
newM, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(newM.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
"snapd_recovery_mode=run static mocked panic=-1 args from gadget",
|
|
})
|
|
|
|
// bootloader variables too
|
|
c.Check(s.bootloader.SetBootVarsCalls, Equals, 1)
|
|
args, err := s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args")
|
|
c.Assert(err, IsNil)
|
|
c.Check(args, DeepEquals, map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": "static mocked panic=-1 args from gadget",
|
|
})
|
|
}
|
|
|
|
func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20ArgsSwitch(c *C) {
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
mockGadgetYaml := `
|
|
volumes:
|
|
volumename:
|
|
bootloader: grub
|
|
`
|
|
sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{
|
|
{"cmdline.extra", "no change"},
|
|
{"meta/gadget.yaml", mockGadgetYaml},
|
|
})
|
|
|
|
s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run static mocked panic=-1 no change"}
|
|
c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil)
|
|
err := s.bootloader.SetBootVars(map[string]string{
|
|
"snapd_extra_cmdline_args": "no change",
|
|
// this is intentionally filled and will be cleared
|
|
"snapd_full_cmdline_args": "canary",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
s.bootloader.SetBootVarsCalls = 0
|
|
|
|
reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf, "")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(reboot, Equals, false)
|
|
|
|
// no reseal needed
|
|
c.Check(s.resealCalls, Equals, 0)
|
|
|
|
newM, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(newM.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run static mocked panic=-1 no change",
|
|
})
|
|
c.Check(s.bootloader.SetBootVarsCalls, Equals, 0)
|
|
args, err := s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args")
|
|
c.Assert(err, IsNil)
|
|
c.Check(args, DeepEquals, map[string]string{
|
|
"snapd_extra_cmdline_args": "no change",
|
|
// canary is still present, as nothing was modified
|
|
"snapd_full_cmdline_args": "canary",
|
|
})
|
|
|
|
// let's change them now
|
|
sfChanged := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{
|
|
{"cmdline.extra", "changed"},
|
|
{"meta/gadget.yaml", mockGadgetYaml},
|
|
})
|
|
|
|
reboot, err = boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sfChanged, "")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(reboot, Equals, true)
|
|
|
|
// reseal was applied
|
|
c.Check(s.resealCalls, Equals, 1)
|
|
c.Check(s.resealCommandLines, DeepEquals, [][]string{{
|
|
// those come from boot chains which use predictable sorting
|
|
"snapd_recovery_mode=run static mocked panic=-1 changed",
|
|
"snapd_recovery_mode=run static mocked panic=-1 no change",
|
|
}})
|
|
|
|
// modeenv has been updated
|
|
newM, err = boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(newM.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run static mocked panic=-1 no change",
|
|
// new ones are appended
|
|
"snapd_recovery_mode=run static mocked panic=-1 changed",
|
|
})
|
|
// and bootloader env too
|
|
args, err = s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args")
|
|
c.Assert(err, IsNil)
|
|
c.Check(args, DeepEquals, map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": "static mocked panic=-1 changed",
|
|
})
|
|
}
|
|
|
|
func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20UnencryptedArgsRemoved(c *C) {
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
mockGadgetYaml := `
|
|
volumes:
|
|
volumename:
|
|
bootloader: grub
|
|
`
|
|
// pretend we used to have additional arguments from the gadget, but
|
|
// those will be gone with new update
|
|
sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{
|
|
{"meta/gadget.yaml", mockGadgetYaml},
|
|
})
|
|
|
|
s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run static mocked panic=-1 from-gadget"}
|
|
c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil)
|
|
err := s.bootloader.SetBootVars(map[string]string{
|
|
"snapd_extra_cmdline_args": "from-gadget",
|
|
// this is intentionally filled and will be cleared
|
|
"snapd_full_cmdline_args": "canary",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
s.bootloader.SetBootVarsCalls = 0
|
|
|
|
reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf, "")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(reboot, Equals, true)
|
|
|
|
c.Check(s.resealCalls, Equals, 1)
|
|
c.Check(s.resealCommandLines, DeepEquals, [][]string{{
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
"snapd_recovery_mode=run static mocked panic=-1 from-gadget",
|
|
}})
|
|
|
|
newM, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(newM.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run static mocked panic=-1 from-gadget",
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
})
|
|
// bootloader variables were explicitly cleared
|
|
c.Check(s.bootloader.SetBootVarsCalls, Equals, 1)
|
|
args, err := s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args")
|
|
c.Assert(err, IsNil)
|
|
c.Check(args, DeepEquals, map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": "static mocked panic=-1",
|
|
})
|
|
}
|
|
|
|
func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20SetError(c *C) {
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
mockGadgetYaml := `
|
|
volumes:
|
|
volumename:
|
|
bootloader: grub
|
|
`
|
|
// pretend we used to have additional arguments from the gadget, but
|
|
// those will be gone with new update
|
|
sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{
|
|
{"cmdline.extra", "this-is-not-applied"},
|
|
{"meta/gadget.yaml", mockGadgetYaml},
|
|
})
|
|
|
|
s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run static mocked panic=-1"}
|
|
c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil)
|
|
|
|
s.bootloader.SetErr = fmt.Errorf("set fails")
|
|
|
|
reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf, "")
|
|
c.Assert(err, ErrorMatches, "cannot set run system kernel command line arguments: set fails")
|
|
c.Assert(reboot, Equals, false)
|
|
// set boot vars was called and failed
|
|
c.Check(s.bootloader.SetBootVarsCalls, Equals, 1)
|
|
|
|
// reseal with new parameters happened though
|
|
c.Check(s.resealCalls, Equals, 1)
|
|
c.Check(s.resealCommandLines, DeepEquals, [][]string{{
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
"snapd_recovery_mode=run static mocked panic=-1 this-is-not-applied",
|
|
}})
|
|
|
|
newM, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(newM.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
// this will be cleared on next reboot or will get overwritten
|
|
// by an update
|
|
"snapd_recovery_mode=run static mocked panic=-1 this-is-not-applied",
|
|
})
|
|
}
|
|
|
|
func (s *bootKernelCommandLineSuite) TestCommandLineUpdateWithResealError(c *C) {
|
|
mockGadgetYaml := `
|
|
volumes:
|
|
volumename:
|
|
bootloader: grub
|
|
`
|
|
gadgetSnap := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{
|
|
{"cmdline.extra", "args from gadget"},
|
|
{"meta/gadget.yaml", mockGadgetYaml},
|
|
})
|
|
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil)
|
|
|
|
resealCalls := 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
return fmt.Errorf("reseal fails")
|
|
})
|
|
defer restore()
|
|
|
|
reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, gadgetSnap, "")
|
|
c.Assert(err, ErrorMatches, "cannot reseal the encryption key: reseal fails")
|
|
c.Check(reboot, Equals, false)
|
|
c.Check(s.bootloader.SetBootVarsCalls, Equals, 0)
|
|
c.Check(resealCalls, Equals, 1)
|
|
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
"snapd_recovery_mode=run static mocked panic=-1 args from gadget",
|
|
})
|
|
}
|
|
|
|
func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20TransitionFullExtraAndBack(c *C) {
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
// no command line arguments from gadget
|
|
s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run static mocked panic=-1"}
|
|
c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil)
|
|
err := s.bootloader.SetBootVars(map[string]string{
|
|
// those are intentionally filled by the test
|
|
"snapd_extra_cmdline_args": "canary",
|
|
"snapd_full_cmdline_args": "canary",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
s.bootloader.SetBootVarsCalls = 0
|
|
|
|
mockGadgetYaml := `
|
|
volumes:
|
|
volumename:
|
|
bootloader: grub
|
|
`
|
|
// transition to gadget with cmdline.extra
|
|
sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{
|
|
{"cmdline.extra", "extra args"},
|
|
{"meta/gadget.yaml", mockGadgetYaml},
|
|
})
|
|
reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf, "")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(reboot, Equals, true)
|
|
c.Check(s.resealCalls, Equals, 1)
|
|
c.Check(s.resealCommandLines, DeepEquals, [][]string{{
|
|
// those come from boot chains which use predictable sorting
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
"snapd_recovery_mode=run static mocked panic=-1 extra args",
|
|
}})
|
|
s.resealCommandLines = nil
|
|
|
|
newM, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(newM.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
"snapd_recovery_mode=run static mocked panic=-1 extra args",
|
|
})
|
|
c.Check(s.bootloader.SetBootVarsCalls, Equals, 1)
|
|
args, err := s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args")
|
|
c.Assert(err, IsNil)
|
|
c.Check(args, DeepEquals, map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": "static mocked panic=-1 extra args",
|
|
})
|
|
// this normally happens after booting
|
|
s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run static mocked panic=-1 extra args"}
|
|
c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil)
|
|
|
|
// transition to full override from gadget
|
|
sfFull := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{
|
|
{"cmdline.full", "full args"},
|
|
{"meta/gadget.yaml", mockGadgetYaml},
|
|
})
|
|
reboot, err = boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sfFull, "")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(reboot, Equals, true)
|
|
c.Check(s.resealCalls, Equals, 2)
|
|
c.Check(s.resealCommandLines, DeepEquals, [][]string{{
|
|
// those come from boot chains which use predictable sorting
|
|
"snapd_recovery_mode=run full args",
|
|
"snapd_recovery_mode=run static mocked panic=-1 extra args",
|
|
}})
|
|
s.resealCommandLines = nil
|
|
// modeenv has been updated
|
|
newM, err = boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(newM.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run static mocked panic=-1 extra args",
|
|
// new ones are appended
|
|
"snapd_recovery_mode=run full args",
|
|
})
|
|
// and bootloader env too
|
|
args, err = s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args")
|
|
c.Assert(err, IsNil)
|
|
c.Check(args, DeepEquals, map[string]string{
|
|
// cleared
|
|
"snapd_extra_cmdline_args": "",
|
|
// and full arguments were set
|
|
"snapd_full_cmdline_args": "full args",
|
|
})
|
|
// this normally happens after booting
|
|
s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run full args"}
|
|
c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil)
|
|
|
|
// transition back to no arguments from the gadget
|
|
sfNone := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{
|
|
{"meta/gadget.yaml", mockGadgetYaml},
|
|
})
|
|
reboot, err = boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sfNone, "")
|
|
|
|
c.Assert(err, IsNil)
|
|
c.Assert(reboot, Equals, true)
|
|
c.Check(s.resealCalls, Equals, 3)
|
|
c.Check(s.resealCommandLines, DeepEquals, [][]string{{
|
|
// those come from boot chains which use predictable sorting
|
|
"snapd_recovery_mode=run full args",
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
}})
|
|
// modeenv has been updated again
|
|
newM, err = boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(newM.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run full args",
|
|
// new ones are appended
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
})
|
|
// and bootloader env too
|
|
args, err = s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args")
|
|
c.Assert(err, IsNil)
|
|
c.Check(args, DeepEquals, map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": "static mocked panic=-1",
|
|
})
|
|
}
|
|
|
|
func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20OverSpuriousRebootsBeforeBootVarsSet(c *C) {
|
|
// simulate spurious reboots
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
resealPanic := false
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
s.resealCalls++
|
|
c.Logf("reseal call %v", s.resealCalls)
|
|
c.Assert(params, NotNil)
|
|
c.Assert(params.ModelParams, HasLen, 1)
|
|
s.resealCommandLines = append(s.resealCommandLines, params.ModelParams[0].KernelCmdlines)
|
|
if resealPanic {
|
|
panic("reseal panic")
|
|
}
|
|
return nil
|
|
})
|
|
defer restore()
|
|
|
|
// no command line arguments from gadget
|
|
s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run static mocked panic=-1"}
|
|
c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil)
|
|
|
|
cmdlineFile := filepath.Join(c.MkDir(), "cmdline")
|
|
err := os.WriteFile(cmdlineFile, []byte("snapd_recovery_mode=run static mocked panic=-1"), 0644)
|
|
c.Assert(err, IsNil)
|
|
restore = kcmdline.MockProcCmdline(cmdlineFile)
|
|
s.AddCleanup(restore)
|
|
|
|
err = s.bootloader.SetBootVars(map[string]string{
|
|
// those are intentionally filled by the test
|
|
"snapd_extra_cmdline_args": "canary",
|
|
"snapd_full_cmdline_args": "canary",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
s.bootloader.SetBootVarsCalls = 0
|
|
|
|
restoreBootloaderNoPanic := s.bootloader.SetMockToPanic("SetBootVars")
|
|
defer restoreBootloaderNoPanic()
|
|
|
|
mockGadgetYaml := `
|
|
volumes:
|
|
volumename:
|
|
bootloader: grub
|
|
`
|
|
// transition to gadget with cmdline.extra
|
|
sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{
|
|
{"cmdline.extra", "extra args"},
|
|
{"meta/gadget.yaml", mockGadgetYaml},
|
|
})
|
|
|
|
// let's panic on reseal first
|
|
resealPanic = true
|
|
c.Assert(func() {
|
|
boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf, "")
|
|
}, PanicMatches, "reseal panic")
|
|
c.Check(s.resealCalls, Equals, 1)
|
|
c.Check(s.resealCommandLines, DeepEquals, [][]string{{
|
|
// those come from boot chains which use predictable sorting
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
"snapd_recovery_mode=run static mocked panic=-1 extra args",
|
|
}})
|
|
// bootenv hasn't been updated yet
|
|
c.Check(s.bootloader.SetBootVarsCalls, Equals, 0)
|
|
// but modeenv has already been updated
|
|
m, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(m.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
"snapd_recovery_mode=run static mocked panic=-1 extra args",
|
|
})
|
|
|
|
// REBOOT
|
|
resealPanic = false
|
|
err = boot.MarkBootSuccessful(s.uc20dev)
|
|
c.Assert(err, IsNil)
|
|
// we resealed after reboot, since modeenv was updated and carries the
|
|
// current command line only
|
|
c.Check(s.resealCalls, Equals, 2)
|
|
m, err = boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(m.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
})
|
|
|
|
// try the update again, but no panic in reseal this time
|
|
s.resealCalls = 0
|
|
s.resealCommandLines = nil
|
|
resealPanic = false
|
|
// but panic in set
|
|
c.Assert(func() {
|
|
boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf, "")
|
|
}, PanicMatches, "mocked reboot panic in SetBootVars")
|
|
c.Check(s.resealCalls, Equals, 1)
|
|
c.Check(s.resealCommandLines, DeepEquals, [][]string{{
|
|
// those come from boot chains which use predictable sorting
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
"snapd_recovery_mode=run static mocked panic=-1 extra args",
|
|
}})
|
|
// the call to bootloader wasn't counted, because it called panic
|
|
c.Check(s.bootloader.SetBootVarsCalls, Equals, 0)
|
|
m, err = boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(m.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
"snapd_recovery_mode=run static mocked panic=-1 extra args",
|
|
})
|
|
|
|
// REBOOT
|
|
err = boot.MarkBootSuccessful(s.uc20dev)
|
|
c.Assert(err, IsNil)
|
|
// we resealed after reboot again
|
|
c.Check(s.resealCalls, Equals, 2)
|
|
m, err = boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(m.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
})
|
|
|
|
// try again, for the last time, things should go smoothly
|
|
s.resealCalls = 0
|
|
s.resealCommandLines = nil
|
|
restoreBootloaderNoPanic()
|
|
reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf, "")
|
|
c.Assert(err, IsNil)
|
|
c.Check(reboot, Equals, true)
|
|
c.Check(s.resealCalls, Equals, 1)
|
|
c.Check(s.bootloader.SetBootVarsCalls, Equals, 1)
|
|
// all done, modeenv
|
|
m, err = boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(m.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
"snapd_recovery_mode=run static mocked panic=-1 extra args",
|
|
})
|
|
args, err := s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args")
|
|
c.Assert(err, IsNil)
|
|
c.Check(args, DeepEquals, map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": "static mocked panic=-1 extra args",
|
|
})
|
|
}
|
|
|
|
func (s *bootKernelCommandLineSuite) TestCommandLineUpdateUC20OverSpuriousRebootsAfterBootVars(c *C) {
|
|
// simulate spurious reboots
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
// no command line arguments from gadget
|
|
s.modeenvWithEncryption.CurrentKernelCommandLines = []string{"snapd_recovery_mode=run static mocked panic=-1"}
|
|
c.Assert(s.modeenvWithEncryption.WriteTo(""), IsNil)
|
|
|
|
cmdlineFile := filepath.Join(c.MkDir(), "cmdline")
|
|
restore := kcmdline.MockProcCmdline(cmdlineFile)
|
|
s.AddCleanup(restore)
|
|
|
|
err := s.bootloader.SetBootVars(map[string]string{
|
|
// those are intentionally filled by the test
|
|
"snapd_extra_cmdline_args": "canary",
|
|
"snapd_full_cmdline_args": "canary",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
s.bootloader.SetBootVarsCalls = 0
|
|
|
|
mockGadgetYaml := `
|
|
volumes:
|
|
volumename:
|
|
bootloader: grub
|
|
`
|
|
// transition to gadget with cmdline.extra
|
|
sf := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, [][]string{
|
|
{"cmdline.extra", "extra args"},
|
|
{"meta/gadget.yaml", mockGadgetYaml},
|
|
})
|
|
|
|
// let's panic after setting bootenv, but before returning, such that if
|
|
// executed by a task handler, the task's status would not get updated
|
|
s.bootloader.SetErrFunc = func() error {
|
|
panic("mocked reboot panic after SetBootVars")
|
|
}
|
|
c.Assert(func() {
|
|
boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf, "")
|
|
}, PanicMatches, "mocked reboot panic after SetBootVars")
|
|
c.Check(s.resealCalls, Equals, 1)
|
|
c.Check(s.resealCommandLines, DeepEquals, [][]string{{
|
|
// those come from boot chains which use predictable sorting
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
"snapd_recovery_mode=run static mocked panic=-1 extra args",
|
|
}})
|
|
// the call to bootloader was executed
|
|
c.Check(s.bootloader.SetBootVarsCalls, Equals, 1)
|
|
m, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(m.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run static mocked panic=-1",
|
|
"snapd_recovery_mode=run static mocked panic=-1 extra args",
|
|
})
|
|
args, err := s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args")
|
|
c.Assert(err, IsNil)
|
|
c.Check(args, DeepEquals, map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": "static mocked panic=-1 extra args",
|
|
})
|
|
|
|
// REBOOT; since we rebooted after updating the bootenv, the kernel
|
|
// command line will include arguments that came from gadget snap
|
|
s.bootloader.SetBootVarsCalls = 0
|
|
s.resealCalls = 0
|
|
err = os.WriteFile(cmdlineFile, []byte("snapd_recovery_mode=run static mocked panic=-1 extra args"), 0644)
|
|
c.Assert(err, IsNil)
|
|
err = boot.MarkBootSuccessful(s.uc20dev)
|
|
c.Assert(err, IsNil)
|
|
// we resealed after reboot again
|
|
c.Check(s.resealCalls, Equals, 1)
|
|
// bootenv wasn't touched
|
|
c.Check(s.bootloader.SetBootVarsCalls, Equals, 0)
|
|
m, err = boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(m.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run static mocked panic=-1 extra args",
|
|
})
|
|
|
|
// try again, as if the task handler gets to run again
|
|
s.resealCalls = 0
|
|
reboot, err := boot.UpdateCommandLineForGadgetComponent(s.uc20dev, sf, "")
|
|
c.Assert(err, IsNil)
|
|
// nothing changed now, we already booted with the new command line
|
|
c.Check(reboot, Equals, false)
|
|
// not reseal since nothing changed
|
|
c.Check(s.resealCalls, Equals, 0)
|
|
// no changes to the bootenv either
|
|
c.Check(s.bootloader.SetBootVarsCalls, Equals, 0)
|
|
// all done, modeenv
|
|
m, err = boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(m.CurrentKernelCommandLines, DeepEquals, boot.BootCommandLines{
|
|
"snapd_recovery_mode=run static mocked panic=-1 extra args",
|
|
})
|
|
args, err = s.bootloader.GetBootVars("snapd_extra_cmdline_args", "snapd_full_cmdline_args")
|
|
c.Assert(err, IsNil)
|
|
c.Check(args, DeepEquals, map[string]string{
|
|
"snapd_extra_cmdline_args": "",
|
|
"snapd_full_cmdline_args": "static mocked panic=-1 extra args",
|
|
})
|
|
}
|
|
|
|
func (s *bootenv20RebootBootloaderSuite) TestCoreParticipant20WithRebootBootloader(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
s.normalDefaultState,
|
|
)
|
|
defer r()
|
|
|
|
// get the boot kernel participant from our new kernel snap
|
|
bootKern := boot.Participant(s.kern2, snap.TypeKernel, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// make the kernel used on next boot
|
|
rebootInfo, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: false})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootInfo.RebootRequired, Equals, true)
|
|
// Test that we get the bootloader options
|
|
c.Assert(rebootInfo.BootloaderOptions, DeepEquals,
|
|
&bootloader.Options{
|
|
Role: bootloader.RoleRunMode,
|
|
})
|
|
|
|
// make sure the env was updated
|
|
m := s.bootloader.BootVars
|
|
c.Assert(m, DeepEquals, map[string]string{
|
|
"kernel_status": boot.TryStatus,
|
|
"snap_kernel": s.kern1.Filename(),
|
|
"snap_try_kernel": s.kern2.Filename(),
|
|
})
|
|
|
|
// and that the modeenv now has this kernel listed
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename(), s.kern2.Filename()})
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20SetNextSameGadgetSnap(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
s.normalDefaultState,
|
|
)
|
|
defer r()
|
|
r = boot.MockResealKeyToModeenv(func(_ string, _ *boot.Modeenv, expectReseal bool, _ boot.Unlocker) error {
|
|
c.Assert(expectReseal, Equals, false)
|
|
return nil
|
|
})
|
|
defer r()
|
|
|
|
// get the gadget participant
|
|
bootGadget := boot.Participant(s.gadget1, snap.TypeGadget, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootGadget.IsTrivial(), Equals, false)
|
|
|
|
// make the gadget used on next boot
|
|
rebootRequired, err := bootGadget.SetNextBoot(boot.NextBootContext{})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: false})
|
|
|
|
// the modeenv is still the same
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.Gadget, Equals, s.gadget1.Filename())
|
|
|
|
// we didn't call SetBootVars on the bootloader (unneeded for gadget)
|
|
c.Assert(s.bootloader.SetBootVarsCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20SetNextNewGadgetSnap(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
s.normalDefaultState,
|
|
)
|
|
defer r()
|
|
r = boot.MockResealKeyToModeenv(func(_ string, _ *boot.Modeenv, expectReseal bool, _ boot.Unlocker) error {
|
|
c.Assert(expectReseal, Equals, false)
|
|
return nil
|
|
})
|
|
defer r()
|
|
|
|
// get the gadget participant
|
|
bootGadget := boot.Participant(s.gadget2, snap.TypeGadget, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootGadget.IsTrivial(), Equals, false)
|
|
|
|
// make the gadget used on next boot
|
|
rebootRequired, err := bootGadget.SetNextBoot(boot.NextBootContext{})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: false})
|
|
|
|
// and that the modeenv now contains gadget2
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.Gadget, Equals, s.gadget2.Filename())
|
|
|
|
// we didn't call SetBootVars on the bootloader (unneeded for gadget)
|
|
c.Assert(s.bootloader.SetBootVarsCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20UndoKernelSnapInstallSame(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
s.normalDefaultState,
|
|
)
|
|
defer r()
|
|
|
|
// get the boot kernel participant from our kernel snap
|
|
bootKern := boot.Participant(s.kern1, snap.TypeKernel, coreDev)
|
|
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// make the kernel used on next boot
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: true})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: false})
|
|
|
|
// make sure that the bootloader was asked for the current kernel
|
|
_, nKernelCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("Kernel")
|
|
c.Assert(nKernelCalls, Equals, 1)
|
|
|
|
// ensure that kernel_status is still empty
|
|
c.Assert(s.bootloader.BootVars["kernel_status"], Equals, boot.DefaultStatus)
|
|
|
|
// there was no attempt to try a kernel
|
|
_, enableKernelCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableTryKernel")
|
|
c.Assert(enableKernelCalls, Equals, 0)
|
|
|
|
// the modeenv is still the same as well
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename()})
|
|
|
|
// finally we didn't call SetBootVars on the bootloader because nothing
|
|
// changed
|
|
c.Assert(s.bootloader.SetBootVarsCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootenv20EnvRefKernelSuite) TestCoreParticipant20UndoKernelSnapInstallSame(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
s.normalDefaultState,
|
|
)
|
|
defer r()
|
|
|
|
// get the boot kernel participant from our kernel snap
|
|
bootKern := boot.Participant(s.kern1, snap.TypeKernel, coreDev)
|
|
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// make the kernel used on next boot
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: true})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: false})
|
|
|
|
// ensure that bootenv is unchanged
|
|
m, err := s.bootloader.GetBootVars("kernel_status", "snap_kernel", "snap_try_kernel")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m, DeepEquals, map[string]string{
|
|
"kernel_status": boot.DefaultStatus,
|
|
"snap_kernel": s.kern1.Filename(),
|
|
"snap_try_kernel": "",
|
|
})
|
|
|
|
// the modeenv is still the same as well
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename()})
|
|
|
|
// finally we didn't call SetBootVars on the bootloader because nothing
|
|
// changed
|
|
c.Assert(s.bootloader.SetBootVarsCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20UndoKernelSnapInstallNew(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
s.normalDefaultState,
|
|
)
|
|
defer r()
|
|
|
|
// get the boot kernel participant from our new kernel snap
|
|
bootKern := boot.Participant(s.kern2, snap.TypeKernel, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// make the kernel used on next boot, reverting the installation
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: true})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, DeepEquals, boot.RebootInfo{
|
|
RebootRequired: true,
|
|
BootloaderOptions: &bootloader.Options{
|
|
Role: bootloader.RoleRunMode,
|
|
},
|
|
})
|
|
|
|
// make sure that the bootloader was asked for the current kernel
|
|
_, nKernelCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("Kernel")
|
|
c.Assert(nKernelCalls, Equals, 1)
|
|
|
|
// ensure that kernel_status is the default
|
|
c.Assert(s.bootloader.BootVars["kernel_status"], Equals, boot.DefaultStatus)
|
|
|
|
// and we were asked to enable kernel2 as kernel, not as try kernel
|
|
_, numTry := s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableTryKernel")
|
|
c.Assert(numTry, Equals, 0)
|
|
actual, _ := s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableKernel")
|
|
c.Assert(actual, DeepEquals, []snap.PlaceInfo{s.kern2})
|
|
|
|
// and that the modeenv now has only this kernel listed
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern2.Filename()})
|
|
}
|
|
|
|
func (s *bootenv20EnvRefKernelSuite) TestCoreParticipant20UndoKernelSnapInstallNew(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
s.normalDefaultState,
|
|
)
|
|
defer r()
|
|
|
|
// get the boot kernel participant from our new kernel snap
|
|
bootKern := boot.Participant(s.kern2, snap.TypeKernel, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// make the kernel used on next boot
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: true})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, DeepEquals, boot.RebootInfo{
|
|
RebootRequired: true,
|
|
BootloaderOptions: &bootloader.Options{
|
|
Role: bootloader.RoleRunMode,
|
|
},
|
|
})
|
|
|
|
// make sure the env was updated
|
|
m := s.bootloader.BootVars
|
|
c.Assert(m, DeepEquals, map[string]string{
|
|
"kernel_status": boot.DefaultStatus,
|
|
"snap_kernel": s.kern2.Filename(),
|
|
"snap_try_kernel": "",
|
|
})
|
|
|
|
// and that the modeenv now has this kernel listed
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern2.Filename()})
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20UndoKernelSnapInstallNewWithReseal(c *C) {
|
|
// checked by resealKeyToModeenv
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
|
|
data := []byte("foobar")
|
|
// SHA3-384
|
|
dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
|
|
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil)
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir), 0755), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil)
|
|
|
|
// mock the files in cache
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"asset-" + dataHash,
|
|
})
|
|
|
|
assetBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir,
|
|
"trusted", fmt.Sprintf("asset-%s", dataHash)), bootloader.RoleRunMode)
|
|
runKernelBf := bootloader.NewBootFile(filepath.Join(s.kern2.Filename()),
|
|
"kernel.efi", bootloader.RoleRunMode)
|
|
|
|
tab.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
|
|
// TODO:UC20: fix mocked trusted assets bootloader to actually
|
|
// geenerate kernel boot files
|
|
runKernelBf,
|
|
}
|
|
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
model := coreDev.Model()
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
},
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
resealCalls := 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
|
|
c.Assert(params.ModelParams, HasLen, 1)
|
|
mp := params.ModelParams[0]
|
|
c.Check(mp.Model.Model(), Equals, model.Model())
|
|
for _, ch := range mp.EFILoadChains {
|
|
printChain(c, ch, "-")
|
|
}
|
|
c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{
|
|
secboot.NewLoadChain(assetBf,
|
|
secboot.NewLoadChain(runKernelBf)),
|
|
})
|
|
// actual paths are seen only here
|
|
c.Check(tab.BootChainKernelPath, DeepEquals, []string{
|
|
s.kern2.MountFile(),
|
|
})
|
|
return nil
|
|
})
|
|
defer restore()
|
|
|
|
// get the boot kernel participant from our new kernel snap
|
|
bootKern := boot.Participant(s.kern2, snap.TypeKernel, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// make the kernel used on next boot
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: true})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, DeepEquals, boot.RebootInfo{
|
|
RebootRequired: true,
|
|
BootloaderOptions: &bootloader.Options{
|
|
Role: bootloader.RoleRunMode,
|
|
},
|
|
})
|
|
|
|
// make sure the env was updated
|
|
bvars, err := tab.GetBootVars("kernel_status", "snap_kernel", "snap_try_kernel")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(bvars, DeepEquals, map[string]string{
|
|
"kernel_status": boot.DefaultStatus,
|
|
"snap_kernel": s.kern2.Filename(),
|
|
"snap_try_kernel": "",
|
|
})
|
|
|
|
// and that the modeenv now has this kernel listed
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern2.Filename()})
|
|
|
|
c.Check(resealCalls, Equals, 1)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20UndoUnassertedKernelSnapInstallNewWithReseal(c *C) {
|
|
// checked by resealKeyToModeenv
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
|
|
data := []byte("foobar")
|
|
// SHA3-384
|
|
dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
|
|
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil)
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir), 0755), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil)
|
|
|
|
// mock the files in cache
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"asset-" + dataHash,
|
|
})
|
|
|
|
assetBf := bootloader.NewBootFile("", filepath.Join(dirs.SnapBootAssetsDir, "trusted", fmt.Sprintf("asset-%s", dataHash)), bootloader.RoleRunMode)
|
|
runKernelBf := bootloader.NewBootFile(filepath.Join(s.ukern2.Filename()), "kernel.efi", bootloader.RoleRunMode)
|
|
|
|
tab.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
|
|
// TODO:UC20: fix mocked trusted assets bootloader to actually
|
|
// geenerate kernel boot files
|
|
runKernelBf,
|
|
}
|
|
|
|
uc20Model := boottest.MakeMockUC20Model()
|
|
coreDev := boottest.MockUC20Device("", uc20Model)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.ukern1.Filename()},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
},
|
|
Model: uc20Model.Model(),
|
|
BrandID: uc20Model.BrandID(),
|
|
Grade: string(uc20Model.Grade()),
|
|
ModelSignKeyID: uc20Model.SignKeyID(),
|
|
}
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.ukern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
resealCalls := 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
|
|
c.Assert(params.ModelParams, HasLen, 1)
|
|
mp := params.ModelParams[0]
|
|
c.Check(mp.Model.Model(), Equals, uc20Model.Model())
|
|
for _, ch := range mp.EFILoadChains {
|
|
printChain(c, ch, "-")
|
|
}
|
|
c.Check(mp.EFILoadChains, DeepEquals, []*secboot.LoadChain{
|
|
secboot.NewLoadChain(assetBf,
|
|
secboot.NewLoadChain(runKernelBf)),
|
|
})
|
|
// actual paths are seen only here
|
|
c.Check(tab.BootChainKernelPath, DeepEquals, []string{
|
|
s.ukern2.MountFile(),
|
|
})
|
|
return nil
|
|
})
|
|
defer restore()
|
|
|
|
// get the boot kernel participant from our new kernel snap
|
|
bootKern := boot.Participant(s.ukern2, snap.TypeKernel, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// make the kernel used on next boot
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: true})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, DeepEquals, boot.RebootInfo{
|
|
RebootRequired: true,
|
|
BootloaderOptions: &bootloader.Options{
|
|
Role: bootloader.RoleRunMode,
|
|
},
|
|
})
|
|
|
|
bvars, err := tab.GetBootVars("kernel_status", "snap_kernel", "snap_try_kernel")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(bvars, DeepEquals, map[string]string{
|
|
"kernel_status": boot.DefaultStatus,
|
|
"snap_kernel": s.ukern2.Filename(),
|
|
"snap_try_kernel": "",
|
|
})
|
|
|
|
// and that the modeenv now has this kernel listed
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.ukern2.Filename()})
|
|
|
|
c.Check(resealCalls, Equals, 1)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20UndoKernelSnapInstallSameNoReseal(c *C) {
|
|
// checked by resealKeyToModeenv
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
|
|
data := []byte("foobar")
|
|
// SHA3-384
|
|
dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
|
|
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil)
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir), 0755), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil)
|
|
|
|
// mock the files in cache
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"asset-" + dataHash,
|
|
})
|
|
|
|
runKernelBf := bootloader.NewBootFile(filepath.Join(s.kern1.Filename()), "kernel.efi", bootloader.RoleRunMode)
|
|
|
|
tab.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
|
|
runKernelBf,
|
|
}
|
|
|
|
uc20Model := boottest.MakeMockUC20Model()
|
|
coreDev := boottest.MockUC20Device("", uc20Model)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
},
|
|
CurrentKernelCommandLines: boot.BootCommandLines{"snapd_recovery_mode=run"},
|
|
|
|
Model: uc20Model.Model(),
|
|
BrandID: uc20Model.BrandID(),
|
|
Grade: string(uc20Model.Grade()),
|
|
ModelSignKeyID: uc20Model.SignKeyID(),
|
|
}
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
resealCalls := 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
return fmt.Errorf("unexpected call to mocked secbootResealKeys")
|
|
})
|
|
defer restore()
|
|
|
|
// get the boot kernel participant from our kernel snap
|
|
bootKern := boot.Participant(s.kern1, snap.TypeKernel, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// write boot-chains for current state that will stay unchanged
|
|
bootChains := []boot.BootChain{{
|
|
BrandID: "my-brand",
|
|
Model: "my-model-uc20",
|
|
Grade: "dangerous",
|
|
ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
|
|
AssetChain: []boot.BootAsset{
|
|
{
|
|
Role: bootloader.RoleRunMode,
|
|
Name: "asset",
|
|
Hashes: []string{
|
|
"0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8",
|
|
},
|
|
},
|
|
},
|
|
Kernel: "pc-kernel",
|
|
KernelRevision: "1",
|
|
KernelCmdlines: []string{"snapd_recovery_mode=run"},
|
|
}}
|
|
err := boot.WriteBootChains(bootChains, filepath.Join(dirs.SnapFDEDir, "boot-chains"), 0)
|
|
c.Assert(err, IsNil)
|
|
|
|
// make the kernel used on next boot
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: true})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: false})
|
|
|
|
// make sure the env is as expected
|
|
bvars, err := tab.GetBootVars("kernel_status", "snap_kernel", "snap_try_kernel")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(bvars, DeepEquals, map[string]string{
|
|
"kernel_status": boot.DefaultStatus,
|
|
"snap_kernel": s.kern1.Filename(),
|
|
"snap_try_kernel": "",
|
|
})
|
|
|
|
// and that the modeenv now has the one kernel listed
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename()})
|
|
|
|
// boot chains were built
|
|
c.Check(tab.BootChainKernelPath, DeepEquals, []string{
|
|
s.kern1.MountFile(),
|
|
})
|
|
// no actual reseal
|
|
c.Check(resealCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20UndoUnassertedKernelSnapInstallSameNoReseal(c *C) {
|
|
// checked by resealKeyToModeenv
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
|
|
data := []byte("foobar")
|
|
// SHA3-384
|
|
dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
|
|
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil)
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSeedDir), 0755), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "asset"), data, 0644), IsNil)
|
|
|
|
// mock the files in cache
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"asset-" + dataHash,
|
|
})
|
|
|
|
runKernelBf := bootloader.NewBootFile(filepath.Join(s.ukern1.Filename()), "kernel.efi", bootloader.RoleRunMode)
|
|
|
|
tab.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
|
|
runKernelBf,
|
|
}
|
|
|
|
uc20Model := boottest.MakeMockUC20Model()
|
|
coreDev := boottest.MockUC20Device("", uc20Model)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.ukern1.Filename()},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
},
|
|
CurrentKernelCommandLines: boot.BootCommandLines{"snapd_recovery_mode=run"},
|
|
|
|
Model: uc20Model.Model(),
|
|
BrandID: uc20Model.BrandID(),
|
|
Grade: string(uc20Model.Grade()),
|
|
ModelSignKeyID: uc20Model.SignKeyID(),
|
|
}
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.ukern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
resealCalls := 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
return fmt.Errorf("unexpected call")
|
|
})
|
|
defer restore()
|
|
|
|
// get the boot kernel participant from our kernel snap
|
|
bootKern := boot.Participant(s.ukern1, snap.TypeKernel, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// write boot-chains for current state that will stay unchanged
|
|
bootChains := []boot.BootChain{{
|
|
BrandID: "my-brand",
|
|
Model: "my-model-uc20",
|
|
Grade: "dangerous",
|
|
ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
|
|
AssetChain: []boot.BootAsset{
|
|
{
|
|
Role: bootloader.RoleRunMode,
|
|
Name: "asset",
|
|
Hashes: []string{
|
|
"0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8",
|
|
},
|
|
},
|
|
},
|
|
Kernel: "pc-kernel",
|
|
KernelRevision: "",
|
|
KernelCmdlines: []string{"snapd_recovery_mode=run"},
|
|
}}
|
|
err := boot.WriteBootChains(bootChains, filepath.Join(dirs.SnapFDEDir, "boot-chains"), 0)
|
|
c.Assert(err, IsNil)
|
|
|
|
// make the kernel used on next boot
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: true})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: false})
|
|
|
|
// make sure the env is as expected
|
|
bvars, err := tab.GetBootVars("kernel_status", "snap_kernel", "snap_try_kernel")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(bvars, DeepEquals, map[string]string{
|
|
"kernel_status": boot.DefaultStatus,
|
|
"snap_kernel": s.ukern1.Filename(),
|
|
"snap_try_kernel": "",
|
|
})
|
|
|
|
// and that the modeenv now has the one kernel listed
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.ukern1.Filename()})
|
|
|
|
// boot chains were built
|
|
c.Check(tab.BootChainKernelPath, DeepEquals, []string{
|
|
s.ukern1.MountFile(),
|
|
})
|
|
// no actual reseal
|
|
c.Check(resealCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20UndoBaseSnapInstallSame(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
// no kernel setup necessary
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// get the boot base participant from our base snap
|
|
bootBase := boot.Participant(s.base1, snap.TypeBase, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootBase.IsTrivial(), Equals, false)
|
|
|
|
// make the base used on next boot
|
|
rebootRequired, err := bootBase.SetNextBoot(boot.NextBootContext{BootWithoutTry: true})
|
|
c.Assert(err, IsNil)
|
|
|
|
// we don't need to reboot because it's the same base snap
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: false})
|
|
|
|
// make sure the modeenv wasn't changed
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.Base, Equals, m.Base)
|
|
c.Assert(m2.BaseStatus, Equals, m.BaseStatus)
|
|
c.Assert(m2.TryBase, Equals, m.TryBase)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20UndoBaseSnapInstallNew(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
// default state
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
// no kernel setup necessary
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// get the boot base participant from our new base snap
|
|
bootBase := boot.Participant(s.base2, snap.TypeBase, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootBase.IsTrivial(), Equals, false)
|
|
|
|
// make the base used on next boot, reverting the current one installed
|
|
rebootRequired, err := bootBase.SetNextBoot(boot.NextBootContext{BootWithoutTry: true})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: true})
|
|
|
|
// make sure the modeenv was updated
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.Base, Equals, s.base2.Filename())
|
|
c.Assert(m2.BaseStatus, Equals, "")
|
|
c.Assert(m2.TryBase, Equals, "")
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20UndoBaseSnapInstallNewNoReseal(c *C) {
|
|
// checked by resealKeyToModeenv
|
|
s.stampSealedKeys(c, dirs.GlobalRootDir)
|
|
|
|
// set up all the bits required for an encrypted system
|
|
tab := s.bootloaderWithTrustedAssets(c, map[string]string{
|
|
"asset": "asset",
|
|
})
|
|
data := []byte("foobar")
|
|
// SHA3-384
|
|
dataHash := "0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8"
|
|
c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir), 0755), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(boot.InitramfsUbuntuBootDir, "asset"), data, 0644), IsNil)
|
|
// mock the files in cache
|
|
mockAssetsCache(c, dirs.GlobalRootDir, "trusted", []string{
|
|
"asset-" + dataHash,
|
|
})
|
|
runKernelBf := bootloader.NewBootFile(filepath.Join(s.kern1.Filename()), "kernel.efi", bootloader.RoleRunMode)
|
|
// write boot-chains for current state that will stay unchanged even
|
|
// though base is changed
|
|
bootChains := []boot.BootChain{{
|
|
BrandID: "my-brand",
|
|
Model: "my-model-uc20",
|
|
Grade: "dangerous",
|
|
ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
|
|
AssetChain: []boot.BootAsset{{
|
|
Role: bootloader.RoleRunMode, Name: "asset", Hashes: []string{
|
|
"0fa8abfbdaf924ad307b74dd2ed183b9a4a398891a2f6bac8fd2db7041b77f068580f9c6c66f699b496c2da1cbcc7ed8",
|
|
},
|
|
}},
|
|
Kernel: "pc-kernel",
|
|
KernelRevision: "1",
|
|
KernelCmdlines: []string{"snapd_recovery_mode=run"},
|
|
}}
|
|
|
|
err := boot.WriteBootChains(boot.ToPredictableBootChains(bootChains), filepath.Join(dirs.SnapFDEDir, "boot-chains"), 0)
|
|
c.Assert(err, IsNil)
|
|
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
model := coreDev.Model()
|
|
|
|
resealCalls := 0
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
return nil
|
|
})
|
|
defer restore()
|
|
|
|
tab.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
|
|
runKernelBf,
|
|
}
|
|
|
|
// default state
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
Base: s.base1.Filename(),
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{dataHash},
|
|
},
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
tab.MockBootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
// no kernel setup necessary
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
// get the boot base participant from our new base snap
|
|
bootBase := boot.Participant(s.base2, snap.TypeBase, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootBase.IsTrivial(), Equals, false)
|
|
|
|
// make the base used on next boot
|
|
rebootRequired, err := bootBase.SetNextBoot(boot.NextBootContext{BootWithoutTry: true})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: true})
|
|
|
|
// make sure the modeenv was updated
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.Base, Equals, s.base2.Filename())
|
|
c.Assert(m2.BaseStatus, Equals, boot.DefaultStatus)
|
|
c.Assert(m2.TryBase, Equals, "")
|
|
|
|
// no reseal
|
|
c.Check(resealCalls, Equals, 0)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestInUseClassicWithModes(c *C) {
|
|
classicWithModesDev := boottest.MockClassicWithModesDevice("", nil)
|
|
c.Assert(classicWithModesDev.IsCoreBoot(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
&bootenv20Setup{
|
|
modeenv: &boot.Modeenv{
|
|
// gadget is gadget1
|
|
Gadget: s.gadget1.Filename(),
|
|
// current kernels is just kern1
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
// operating mode is run
|
|
Mode: "run",
|
|
// RecoverySystem is unset, as it should be during run mode
|
|
RecoverySystem: "",
|
|
},
|
|
// enabled kernel is kern1
|
|
kern: s.kern1,
|
|
// no try kernel enabled
|
|
tryKern: nil,
|
|
// kernel status is default
|
|
kernStatus: boot.DefaultStatus,
|
|
})
|
|
defer r()
|
|
|
|
inUse, err := boot.InUse(snap.TypeKernel, classicWithModesDev)
|
|
c.Check(err, IsNil)
|
|
c.Check(inUse(s.kern1.SnapName(), s.kern1.SnapRevision()), Equals, true)
|
|
c.Check(inUse(s.kern2.SnapName(), s.kern2.SnapRevision()), Equals, false)
|
|
|
|
_, err = boot.InUse(snap.TypeBase, classicWithModesDev)
|
|
c.Check(err, IsNil)
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestCoreParticipant20SetNextCurrentKernelSnap(c *C) {
|
|
coreDev := boottest.MockUC20Device("", nil)
|
|
c.Assert(coreDev.HasModeenv(), Equals, true)
|
|
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
s.normalDefaultState,
|
|
)
|
|
defer r()
|
|
|
|
// get the boot kernel participant from our current kernel snap
|
|
bootKern := boot.Participant(s.kern1, snap.TypeKernel, coreDev)
|
|
// make sure it's not a trivial boot participant
|
|
c.Assert(bootKern.IsTrivial(), Equals, false)
|
|
|
|
// Make it the kernel used on next boot. This sort of situation (same
|
|
// current and next kernel) can happen when an installation of a new
|
|
// kernel is aborted before we reboot: in that case we need to clean up
|
|
// some things although the current kernel did not really change.
|
|
rebootRequired, err := bootKern.SetNextBoot(boot.NextBootContext{BootWithoutTry: true})
|
|
c.Assert(err, IsNil)
|
|
c.Assert(rebootRequired, Equals, boot.RebootInfo{RebootRequired: false})
|
|
|
|
// make sure that the bootloader was asked for the current kernel
|
|
_, nKernelCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("Kernel")
|
|
c.Assert(nKernelCalls, Equals, 1)
|
|
|
|
// ensure that kernel_status is not set
|
|
c.Assert(s.bootloader.BootVars["kernel_status"], Equals, "")
|
|
|
|
// we were not asked to enable a try kernel
|
|
actual, _ := s.bootloader.GetRunKernelImageFunctionSnapCalls("EnableTryKernel")
|
|
c.Assert(len(actual), Equals, 0)
|
|
|
|
// and we were asked to disable the try kernel
|
|
_, nDisableTryCalls := s.bootloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel")
|
|
c.Assert(nDisableTryCalls, Equals, 1)
|
|
|
|
// and that the modeenv has this kernel listed only once
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(m2.CurrentKernels, DeepEquals, []string{s.kern1.Filename()})
|
|
}
|
|
|
|
func (s *bootenv20Suite) TestMarkBootSuccessfulClassModes(c *C) {
|
|
// MarkBootSuccessful on classic+modes will not have a "base"
|
|
// in the modeenv
|
|
m := &boot.Modeenv{
|
|
Mode: "run",
|
|
CurrentKernels: []string{s.kern1.Filename()},
|
|
}
|
|
r := setupUC20Bootenv(
|
|
c,
|
|
s.bootloader,
|
|
&bootenv20Setup{
|
|
modeenv: m,
|
|
kern: s.kern1,
|
|
kernStatus: boot.DefaultStatus,
|
|
},
|
|
)
|
|
defer r()
|
|
|
|
classicWithModesDev := boottest.MockClassicWithModesDevice("", nil)
|
|
c.Assert(classicWithModesDev.HasModeenv(), Equals, true)
|
|
|
|
// mark successful
|
|
err := boot.MarkBootSuccessful(classicWithModesDev)
|
|
c.Assert(err, IsNil)
|
|
|
|
// no error, modeenv is unchanged
|
|
m2, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(m2.Base, Equals, "")
|
|
c.Check(m2.TryBase, Equals, "")
|
|
}
|