Skip to content

Commit

Permalink
feat: support kexec from uki
Browse files Browse the repository at this point in the history
Support kexec from UKI for non-secureboot by extracting kernel,
initramfs and cmdline from UKI

Fixes: #10189

Signed-off-by: Noel Georgi <[email protected]>
frezbo committed Jan 28, 2025
1 parent 8da2649 commit 42e1669
Showing 6 changed files with 215 additions and 86 deletions.
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ package bootloader
import (
"os"

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/sdboot"
@@ -27,6 +28,9 @@ type Bootloader interface {
Revert(disk string) error
// RequiredPartitions returns the required partitions for the bootloader.
RequiredPartitions() []partition.Options

// KexecLoad does a kexec_file_load using the current entry of the bootloader.
KexecLoad(r runtime.Runtime, disk string) error
}

// Probe checks if any supported bootloaders are installed.
46 changes: 46 additions & 0 deletions internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/grub.go
Original file line number Diff line number Diff line change
@@ -8,8 +8,14 @@ package grub
import (
"errors"
"fmt"
"log"
"os"
"path/filepath"
"strings"

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/kexec"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options"
"github.com/siderolabs/talos/internal/pkg/partition"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/version"
@@ -44,6 +50,46 @@ func NewConfig() *Config {
}
}

// KexecLoad does a kexec using the bootloader config.
func (c *Config) KexecLoad(r runtime.Runtime, disk string) error {
_, err := ProbeWithCallback(disk, options.ProbeOptions{}, func(grubConf *Config) error {
defaultEntry, ok := grubConf.Entries[grubConf.Default]

if !ok {
return nil
}

kernelPath := filepath.Join(constants.BootMountPoint, defaultEntry.Linux)
initrdPath := filepath.Join(constants.BootMountPoint, defaultEntry.Initrd)

kernel, err := os.Open(kernelPath)
if err != nil {
return err
}

defer kernel.Close() //nolint:errcheck

initrd, err := os.Open(initrdPath)
if err != nil {
return err
}

defer initrd.Close() //nolint:errcheck

cmdline := strings.TrimSpace(defaultEntry.Cmdline)

if err = kexec.Load(r, kernel, int(initrd.Fd()), cmdline); err != nil {
return err
}

log.Printf("prepared kexec environment kernel=%q initrd=%q cmdline=%q", kernelPath, initrdPath, cmdline)

return nil
})

return err
}

// RequiredPartitions returns the list of partitions required by the bootloader.
func (c *Config) RequiredPartitions() []partition.Options {
return []partition.Options{
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package kexec call unix.KexecFileLoad with error handling.
package kexec

import (
"errors"
"fmt"
"io"
"log"
"os"
goruntime "runtime"

"golang.org/x/sys/unix"

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/internal/pkg/zboot"
)

// Load handles zboot for arm64 and calls unix.KexecFileLoad with error handling and sets the machine state to kexec prepared.
func Load(r runtime.Runtime, kernel *os.File, initrdFD int, cmdline string) error {
kernelFD := int(kernel.Fd())

// on arm64 we need to extract the kernel from the zboot image if it's compressed
if goruntime.GOARCH == "arm64" {
var (
fileCloser io.Closer
extractErr error
)

kernelFD, fileCloser, extractErr = zboot.Extract(kernel)
if extractErr != nil {
return fmt.Errorf("failed to extract kernel from zboot: %w", extractErr)
}

defer func() {
if fileCloser != nil {
fileCloser.Close() //nolint:errcheck
}
}()
}

if err := unix.KexecFileLoad(kernelFD, initrdFD, cmdline, 0); err != nil {
switch {
case errors.Is(err, unix.ENOSYS):
log.Printf("kexec support is disabled in the kernel")

return nil
case errors.Is(err, unix.EPERM):
log.Printf("kexec support is disabled via sysctl")

return nil
case errors.Is(err, unix.EBUSY):
log.Printf("kexec is busy")

return nil
default:
return fmt.Errorf("error loading kernel for kexec: %w", err)
}
}

r.State().Machine().KexecPrepared(true)

return nil
}
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ package sdboot
import (
"errors"
"fmt"
"io"
"log"
"os"
"path/filepath"
@@ -16,11 +17,15 @@ import (
"github.com/ecks/uefi/efi/efivario"
"github.com/siderolabs/gen/xerrors"
"github.com/siderolabs/go-blockdevice/v2/blkid"
"golang.org/x/sys/unix"

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/kexec"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/mount"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options"
mountv2 "github.com/siderolabs/talos/internal/pkg/mount/v2"
"github.com/siderolabs/talos/internal/pkg/partition"
"github.com/siderolabs/talos/internal/pkg/uki"
"github.com/siderolabs/talos/pkg/imager/utils"
"github.com/siderolabs/talos/pkg/machinery/constants"
)
@@ -52,10 +57,10 @@ func New() *Config {
return &Config{}
}

// Probe for existing sd-boot bootloader.
// ProbeWithCallback probes the sd-boot bootloader, and calls the callback function with the Config.
//
//nolint:gocyclo
func Probe(disk string, options options.ProbeOptions) (*Config, error) {
func ProbeWithCallback(disk string, options options.ProbeOptions, callback func(*Config) error) (*Config, error) {
// if not UEFI boot, nothing to do
if !isUEFIBoot() {
return nil, nil
@@ -137,6 +142,12 @@ func Probe(disk string, options options.ProbeOptions) (*Config, error) {

for _, file := range files {
if strings.EqualFold(filepath.Base(file), bootedEntry) {
if callback != nil {
return callback(&Config{
Default: bootedEntry,
})
}

return nil
}
}
@@ -160,6 +171,75 @@ func Probe(disk string, options options.ProbeOptions) (*Config, error) {
}, nil
}

// Probe for existing sd-boot bootloader.
func Probe(disk string, options options.ProbeOptions) (*Config, error) {
return ProbeWithCallback(disk, options, nil)
}

// KexecLoad does a kexec using the bootloader config.
func (c *Config) KexecLoad(r runtime.Runtime, disk string) error {
_, err := ProbeWithCallback(disk, options.ProbeOptions{}, func(conf *Config) error {
var kernelFd int

assetInfo, err := uki.Extract(filepath.Join(constants.EFIMountPoint, "EFI", "Linux", conf.Default))
if err != nil {
return fmt.Errorf("failed to extract kernel and initrd from uki: %w", err)
}

defer assetInfo.Close() //nolint:errcheck

kernelFd, err = unix.MemfdCreate("vmlinux", 0)
if err != nil {
return fmt.Errorf("memfdCreate: %v", err)
}

kernelMemfd := os.NewFile(uintptr(kernelFd), "vmlinux")

defer kernelMemfd.Close() //nolint:errcheck

if _, err := io.Copy(kernelMemfd, assetInfo.Kernel); err != nil {
return fmt.Errorf("failed to read kernel from uki: %w", err)
}

if _, err = kernelMemfd.Seek(0, io.SeekStart); err != nil {
return fmt.Errorf("failed to seek kernel: %w", err)
}

initrdFd, err := unix.MemfdCreate("initrd", 0)
if err != nil {
return fmt.Errorf("memfdCreate: %v", err)
}

initrdMemfd := os.NewFile(uintptr(initrdFd), "initrd")

defer initrdMemfd.Close() //nolint:errcheck

if _, err := io.Copy(initrdMemfd, assetInfo.Initrd); err != nil {
return fmt.Errorf("failed to read initrd from uki: %w", err)
}

if _, err = initrdMemfd.Seek(0, io.SeekStart); err != nil {
return fmt.Errorf("failed to seek initrd: %w", err)
}

var cmdline strings.Builder

if _, err := io.Copy(&cmdline, assetInfo.Cmdline); err != nil {
return fmt.Errorf("failed to read cmdline from uki: %w", err)
}

if err := kexec.Load(r, kernelMemfd, initrdFd, cmdline.String()); err != nil {
return fmt.Errorf("failed to load kernel for kexec: %w", err)
}

log.Printf("prepared kexec environment with kernel and initrd extracted from uki, cmdline=%q", cmdline.String())

return nil
})

return err
}

// RequiredPartitions returns the list of partitions required by the bootloader.
func (c *Config) RequiredPartitions() []partition.Options {
return []partition.Options{
Loading

0 comments on commit 42e1669

Please sign in to comment.