Skip to content

Commit

Permalink
many: make kernel-modules components available on first boot (#14744)
Browse files Browse the repository at this point in the history
* boot: copy kernel-modules components on installation

* gadget: consider kernel-modules components when writing content

Make sure that kernel-modules components are taken into account when
creating the kernel tree on installation, also make sure that the
respective mount units are in place too.

* o/devicestate: add kernel-modules components to KernelSnapInfo

We need this information to have them early installed on first boot
after installation.

* seed/seedtest: add extra snap and components

* o/devicestate: consider kernel-modules components on API installs

Make sure that kernel-modules components are considered when an
installation is preformed using the snapd API, so the modules are
available on early (first) boot.

This is ensured by
- Including these components in the drivers tree
- Making sure there are mount units for them
- Copying the components to the data partition

* o/devicestate: consider mode when retrieving kernel-modules components

Retrieve only kernel-modules for run mode when enabling them on
installation.

* tests/muinstaller-real: test kernel-modules installation

Make sure that modules from kernel-modules components
- are available early on first boot
- are available early after rebooting in a joint refresh with a kernel

* boot,overlord: minor fixes

* cmd/snap-bootstrap: add TODO for kernel-modules components

* tests: generalize build_kernel_with_comps.sh

Change build_kernel_with_comps.sh so it can build a kernel-modules
component for any desired kernel module. Use it in
kernel-modules-components test.

* tests/build-with-kernel-modules-components: add new test

build-with-kernel-modules-components checks that when we build or do a
factory reset with a seed including a kernel-modules component, the
included kernel modules are available on early boot.

* tests/lib/nested.sh: add support to create images with components

* o/devicestate: move types around to fix nosectoot unit tests

* tests/muinstaller-real: fix modules path
  • Loading branch information
alfonsosanchezbeato authored Dec 5, 2024
1 parent 0268f05 commit 82ef1e2
Show file tree
Hide file tree
Showing 17 changed files with 1,759 additions and 518 deletions.
39 changes: 28 additions & 11 deletions boot/makebootable.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ type BootableSet struct {

// Recovery is set when making the recovery partition bootable.
Recovery bool

// KernelMods contains kernel-modules components in the system.
KernelMods []BootableKModsComponents
}

// BootableComponent represents kernel-modules components, which are
// needed as part of a BootableSet.
type BootableKModsComponents struct {
// CompPlaceInfo is used to build the file name with the right revision.
CompPlaceInfo snap.ContainerPlaceInfo
// CompPath is the path where we will copy the file from.
CompPath string
}

// MakeBootableImage sets up the given bootable set and target filesystem
Expand Down Expand Up @@ -334,7 +346,7 @@ type makeRunnableOptions struct {
StateUnlocker Unlocker
}

func copyBootSnap(orig string, dstInfo *snap.Info, dstSnapBlobDir string) error {
func copyBootSnap(orig string, filename string, dstSnapBlobDir string) error {
// if the source path is a symlink, don't copy the symlink, copy the
// target file instead of copying the symlink, as the initramfs won't
// follow the symlink when it goes to mount the base and kernel snaps by
Expand All @@ -347,10 +359,7 @@ func copyBootSnap(orig string, dstInfo *snap.Info, dstSnapBlobDir string) error
}
orig = link
}
// note that we need to use the "Filename()" here because unasserted
// snaps will have names like pc-kernel_5.19.4.snap but snapd expects
// "pc-kernel_x1.snap"
dst := filepath.Join(dstSnapBlobDir, dstInfo.Filename())
dst := filepath.Join(dstSnapBlobDir, filename)
if err := osutil.CopyFile(orig, dst, osutil.CopyFlagPreserveAll|osutil.CopyFlagSync); err != nil {
return err
}
Expand All @@ -372,19 +381,27 @@ func makeRunnableSystem(model *asserts.Model, bootWith *BootableSet, observer Tr
// install the boot.sel onto ubuntu-boot directly, but the file should be
// managed by snapd instead

// copy kernel/base/gadget into the ubuntu-data partition
// Copy kernel/base/gadget and kernel-modules components into the
// ubuntu-data partition. Note that we need to use the "Filename()"
// here because unasserted snaps/components will have names like
// pc-kernel_5.19.4.snap but snapd expects "pc-kernel_x1.snap"
snapBlobDir := dirs.SnapBlobDirUnder(InstallHostWritableDir(model))
if err := os.MkdirAll(snapBlobDir, 0755); err != nil {
return err
}
for _, origDest := range []struct {
orig string
destInfo *snap.Info
fileName string
}{
{orig: bootWith.BasePath, destInfo: bootWith.Base},
{orig: bootWith.KernelPath, destInfo: bootWith.Kernel},
{orig: bootWith.GadgetPath, destInfo: bootWith.Gadget}} {
if err := copyBootSnap(origDest.orig, origDest.destInfo, snapBlobDir); err != nil {
{orig: bootWith.BasePath, fileName: bootWith.Base.Filename()},
{orig: bootWith.KernelPath, fileName: bootWith.Kernel.Filename()},
{orig: bootWith.GadgetPath, fileName: bootWith.Gadget.Filename()}} {
if err := copyBootSnap(origDest.orig, origDest.fileName, snapBlobDir); err != nil {
return err
}
}
for _, kmod := range bootWith.KernelMods {
if err := copyBootSnap(kmod.CompPath, kmod.CompPlaceInfo.Filename(), snapBlobDir); err != nil {
return err
}
}
Expand Down
156 changes: 103 additions & 53 deletions boot/makebootable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,8 +497,16 @@ func (s *makeBootable20Suite) TestMakeSystemRunnable16Fails(c *C) {
c.Assert(err, ErrorMatches, `internal error: cannot make pre-UC20 system runnable`)
}

func (s *makeBootable20Suite) testMakeSystemRunnable20(c *C, standalone, factoryReset, classic bool, fromInitrd bool) {
restore := release.MockOnClassic(classic)
type testMakeSystemRunnable20Opts struct {
standalone bool
factoryReset bool
classic bool
fromInitrd bool
withKComps bool
}

func (s *makeBootable20Suite) testMakeSystemRunnable20(c *C, opts testMakeSystemRunnable20Opts) {
restore := release.MockOnClassic(opts.classic)
defer restore()
dirs.SetRootDir(dirs.GlobalRootDir)

Expand All @@ -512,7 +520,7 @@ func (s *makeBootable20Suite) testMakeSystemRunnable20(c *C, standalone, factory
})()

var model *asserts.Model
if classic {
if opts.classic {
model = boottest.MakeMockUC20Model(map[string]interface{}{
"classic": "true",
"distribution": "ubuntu",
Expand Down Expand Up @@ -588,6 +596,16 @@ version: 5.0
gadgetInSeed := filepath.Join(seedSnapsDirs, gadgetInfo.Filename())
err = os.Symlink(gadgetFn, gadgetInSeed)
c.Assert(err, IsNil)
kModsComps := []boot.BootableKModsComponents{}
if opts.withKComps {
for _, compName := range []string{"kcomp1", "kcomp2"} {
compFn := snaptest.MakeTestComponentWithFiles(c, compName,
"component: pc-kernel+kcomp1\ntype: kernel-modules\n", nil)
cpi := snap.MinimalComponentContainerPlaceInfo(compName,
snap.R(33), "pc-kernel")
kModsComps = append(kModsComps, boot.BootableKModsComponents{cpi, compFn})
}
}
kernelFn, kernelInfo := makeSnapWithFiles(c, "pc-kernel", `name: pc-kernel
type: kernel
version: 5.0
Expand All @@ -610,6 +628,7 @@ version: 5.0
Kernel: kernelInfo,
Recovery: false,
UnpackedGadgetDir: unpackedGadgetDir,
KernelMods: kModsComps,
}

// set up observer state
Expand Down Expand Up @@ -639,7 +658,7 @@ version: 5.0
// set a mock recovery kernel
readSystemEssentialCalls := 0
restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
if fromInitrd {
if opts.fromInitrd {
c.Assert(seedDir, Equals, filepath.Join(boot.InitramfsRunMntDir, "ubuntu-seed"))
} else {
c.Assert(seedDir, Equals, dirs.SnapSeedDir)
Expand All @@ -653,7 +672,7 @@ version: 5.0
restore = boot.MockSecbootProvisionTPM(func(mode secboot.TPMProvisionMode, lockoutAuthFile string) error {
provisionCalls++
c.Check(lockoutAuthFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-lockout-auth"))
if factoryReset {
if opts.factoryReset {
c.Check(mode, Equals, secboot.TPMPartialReprovision)
} else {
c.Check(mode, Equals, secboot.TPMProvisionFull)
Expand All @@ -666,7 +685,7 @@ version: 5.0
restore = boot.MockSecbootPCRHandleOfSealedKey(func(p string) (uint32, error) {
pcrHandleOfKeyCalls++
c.Check(provisionCalls, Equals, 0)
if !factoryReset {
if !opts.factoryReset {
c.Errorf("unexpected call in non-factory-reset scenario")
return 0, fmt.Errorf("unexpected call")
}
Expand All @@ -679,7 +698,7 @@ version: 5.0

releasePCRHandleCalls := 0
restore = boot.MockSecbootReleasePCRResourceHandles(func(handles ...uint32) error {
c.Check(factoryReset, Equals, true)
c.Check(opts.factoryReset, Equals, true)
releasePCRHandleCalls++
c.Check(handles, DeepEquals, []uint32{
secboot.AltRunObjectPCRPolicyCounterHandle,
Expand Down Expand Up @@ -708,7 +727,7 @@ version: 5.0
c.Check(keys[0].Key, DeepEquals, myKey)
c.Check(keys[0].KeyFile, Equals,
filepath.Join(s.rootdir, "/run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key"))
if factoryReset {
if opts.factoryReset {
c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltRunObjectPCRPolicyCounterHandle)
} else {
c.Check(params.PCRPolicyCounterHandle, Equals, secboot.RunObjectPCRPolicyCounterHandle)
Expand All @@ -720,7 +739,7 @@ version: 5.0
c.Check(keys[0].KeyFile, Equals,
filepath.Join(s.rootdir,
"/run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key"))
if factoryReset {
if opts.factoryReset {
c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltFallbackObjectPCRPolicyCounterHandle)
c.Check(keys[1].KeyFile, Equals,
filepath.Join(s.rootdir,
Expand Down Expand Up @@ -750,11 +769,11 @@ version: 5.0
var runKernelPath string
var runKernel bootloader.BootFile
switch {
case !standalone:
case !opts.standalone:
runKernelPath = "/var/lib/snapd/snaps/pc-kernel_5.snap"
case classic:
case opts.classic:
runKernelPath = "/run/mnt/ubuntu-data/var/lib/snapd/snaps/pc-kernel_5.snap"
case !classic:
case !opts.classic:
runKernelPath = "/run/mnt/ubuntu-data/system-data/var/lib/snapd/snaps/pc-kernel_5.snap"
}
runKernel = bootloader.NewBootFile(filepath.Join(s.rootdir, runKernelPath), "kernel.efi", bootloader.RoleRunMode)
Expand Down Expand Up @@ -788,13 +807,13 @@ version: 5.0
defer restore()

switch {
case standalone && fromInitrd:
case opts.standalone && opts.fromInitrd:
err = boot.MakeRunnableStandaloneSystemFromInitrd(model, bootWith, obs)
case standalone && !fromInitrd:
case opts.standalone && !opts.fromInitrd:
u := mockUnlocker{}
err = boot.MakeRunnableStandaloneSystem(model, bootWith, obs, u.unlocker)
c.Check(u.unlocked, Equals, 1)
case factoryReset && !fromInitrd:
case opts.factoryReset && !opts.fromInitrd:
err = boot.MakeRunnableSystemAfterDataReset(model, bootWith, obs)
default:
err = boot.MakeRunnableSystem(model, bootWith, obs)
Expand All @@ -811,7 +830,7 @@ version: 5.0
c.Check(mockBootGrubCfg, testutil.FileEquals, string(grubCfgAsset))

var installHostWritableDir string
if classic {
if opts.classic {
installHostWritableDir = filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data")
} else {
installHostWritableDir = filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-data/system-data")
Expand All @@ -826,6 +845,13 @@ version: 5.0
c.Check(pcKernelSnap, testutil.FilePresent)
c.Check(osutil.IsSymlink(core20Snap), Equals, false)
c.Check(osutil.IsSymlink(pcKernelSnap), Equals, false)
if opts.withKComps {
for _, compName := range []string{"kcomp1", "kcomp2"} {
comp := filepath.Join(dirs.SnapBlobDirUnder(installHostWritableDir),
"pc-kernel+"+compName+"_33.comp")
c.Check(comp, testutil.FilePresent)
}
}

// ensure the bootvars got updated the right way
mockSeedGrubenv := filepath.Join(mockSeedGrubDir, "grubenv")
Expand All @@ -852,7 +878,7 @@ version: 5.0

// ensure modeenv looks correct
var ubuntuDataModeEnvPath, classicLine, base string
if classic {
if opts.classic {
base = ""
ubuntuDataModeEnvPath = filepath.Join(s.rootdir, "/run/mnt/ubuntu-data/var/lib/snapd/modeenv")
classicLine = "\nclassic=true"
Expand Down Expand Up @@ -908,7 +934,7 @@ current_kernel_command_lines=["snapd_recovery_mode=run console=ttyS0 console=tty
// make sure SealKey was called for the run object and the fallback object
c.Check(sealKeysCalls, Equals, 2)
// PCR handle checks
if factoryReset {
if opts.factoryReset {
c.Check(pcrHandleOfKeyCalls, Equals, 1)
c.Check(releasePCRHandleCalls, Equals, 1)
} else {
Expand All @@ -924,43 +950,63 @@ current_kernel_command_lines=["snapd_recovery_mode=run console=ttyS0 console=tty
}

func (s *makeBootable20Suite) TestMakeSystemRunnable20Install(c *C) {
const standalone = false
const factoryReset = false
const classic = false
const fromInitrd = false
s.testMakeSystemRunnable20(c, standalone, factoryReset, classic, fromInitrd)
s.testMakeSystemRunnable20(c, testMakeSystemRunnable20Opts{
standalone: false,
factoryReset: false,
classic: false,
fromInitrd: false,
withKComps: false,
})
}

func (s *makeBootable20Suite) TestMakeSystemRunnable20InstallWithKComps(c *C) {
s.testMakeSystemRunnable20(c, testMakeSystemRunnable20Opts{
standalone: false,
factoryReset: false,
classic: false,
fromInitrd: false,
withKComps: true,
})
}

func (s *makeBootable20Suite) TestMakeSystemRunnable20InstallOnClassic(c *C) {
const standalone = false
const factoryReset = false
const classic = true
const fromInitrd = false
s.testMakeSystemRunnable20(c, standalone, factoryReset, classic, fromInitrd)
s.testMakeSystemRunnable20(c, testMakeSystemRunnable20Opts{
standalone: false,
factoryReset: false,
classic: true,
fromInitrd: false,
withKComps: true,
})
}

func (s *makeBootable20Suite) TestMakeSystemRunnable20FactoryReset(c *C) {
const standalone = false
const factoryReset = true
const classic = false
const fromInitrd = false
s.testMakeSystemRunnable20(c, standalone, factoryReset, classic, fromInitrd)
s.testMakeSystemRunnable20(c, testMakeSystemRunnable20Opts{
standalone: false,
factoryReset: true,
classic: false,
fromInitrd: false,
withKComps: true,
})
}

func (s *makeBootable20Suite) TestMakeSystemRunnable20FactoryResetOnClassic(c *C) {
const standalone = false
const factoryReset = true
const classic = true
const fromInitrd = false
s.testMakeSystemRunnable20(c, standalone, factoryReset, classic, fromInitrd)
s.testMakeSystemRunnable20(c, testMakeSystemRunnable20Opts{
standalone: false,
factoryReset: true,
classic: true,
fromInitrd: false,
withKComps: true,
})
}

func (s *makeBootable20Suite) TestMakeSystemRunnable20InstallFromInitrd(c *C) {
const standalone = true
const factoryReset = false
const classic = false
const fromInitrd = true
s.testMakeSystemRunnable20(c, standalone, factoryReset, classic, fromInitrd)
s.testMakeSystemRunnable20(c, testMakeSystemRunnable20Opts{
standalone: true,
factoryReset: false,
classic: false,
fromInitrd: true,
withKComps: true,
})
}

func (s *makeBootable20Suite) TestMakeRunnableSystem20ModeInstallBootConfigErr(c *C) {
Expand Down Expand Up @@ -2276,19 +2322,23 @@ current_kernel_command_lines=["snapd_recovery_mode=run console=ttyS0 console=tty
}

func (s *makeBootable20Suite) TestMakeStandaloneSystemRunnable20Install(c *C) {
const standalone = true
const factoryReset = false
const classic = false
const fromInitrd = false
s.testMakeSystemRunnable20(c, standalone, factoryReset, classic, fromInitrd)
s.testMakeSystemRunnable20(c, testMakeSystemRunnable20Opts{
standalone: true,
factoryReset: false,
classic: false,
fromInitrd: false,
withKComps: true,
})
}

func (s *makeBootable20Suite) TestMakeStandaloneSystemRunnable20InstallOnClassic(c *C) {
const standalone = true
const factoryReset = false
const classic = true
const fromInitrd = false
s.testMakeSystemRunnable20(c, standalone, factoryReset, classic, fromInitrd)
s.testMakeSystemRunnable20(c, testMakeSystemRunnable20Opts{
standalone: true,
factoryReset: false,
classic: true,
fromInitrd: false,
withKComps: true,
})
}

func (s *makeBootable20Suite) testMakeBootableImageOptionalKernelArgs(c *C, model *asserts.Model, options map[string]string, expectedCmdline, errMsg string) {
Expand Down
4 changes: 4 additions & 0 deletions cmd/snap-bootstrap/cmd_initramfs_mounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,10 @@ func doInstall(mst *initramfsMountsState, model *asserts.Model, sysSnaps map[sna
return fmt.Errorf("cannot use gadget: %v", err)
}

// TODO:COMPS take into account kernel-modules components, see
// DeviceManager,doSetupRunSystem and other parts of
// handlers_install.go.

bootDevice := ""
kernelSnapInfo := &gadgetInstall.KernelSnapInfo{
Name: kernelSnap.SnapName(),
Expand Down
Loading

0 comments on commit 82ef1e2

Please sign in to comment.