Skip to content

Commit 091da16

Browse files
committed
chore: support arm64 kexec from zboot kernel images
When using kernel images that are using ZBOOT for arm64 we need to extract the vmlinux from the vmlinuz EFI file and pass it on the the kexec call. Ref: https://git.kernel.org/pub/scm/utils/kernel/kexec/kexec-tools.git/tree/kexec/kexec-pe-zboot.c Fixes: #8907 Signed-off-by: Noel Georgi <git@frezbo.dev>
1 parent 73511c1 commit 091da16

File tree

2 files changed

+100
-1
lines changed

2 files changed

+100
-1
lines changed

internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"log"
1616
"os"
1717
"path/filepath"
18+
goruntime "runtime"
1819
"slices"
1920
"strconv"
2021
"strings"
@@ -64,6 +65,7 @@ import (
6465
"github.com/siderolabs/talos/internal/pkg/partition"
6566
"github.com/siderolabs/talos/internal/pkg/secureboot"
6667
"github.com/siderolabs/talos/internal/pkg/secureboot/tpm2"
68+
"github.com/siderolabs/talos/internal/zboot"
6769
"github.com/siderolabs/talos/pkg/conditions"
6870
"github.com/siderolabs/talos/pkg/images"
6971
"github.com/siderolabs/talos/pkg/kernel/kspp"
@@ -2141,6 +2143,24 @@ func KexecPrepare(_ runtime.Sequence, data any) (runtime.TaskExecutionFunc, stri
21412143

21422144
defer kernel.Close() //nolint:errcheck
21432145

2146+
fd := int(kernel.Fd())
2147+
2148+
// on arm64 we need to extract the kernel from the zboot image if it's compressed
2149+
if goruntime.GOARCH == "arm64" {
2150+
var fileCloser io.Closer
2151+
2152+
fd, fileCloser, err = zboot.Extract(kernel)
2153+
if err != nil {
2154+
return err
2155+
}
2156+
2157+
defer func() {
2158+
if fileCloser != nil {
2159+
fileCloser.Close() //nolint:errcheck
2160+
}
2161+
}()
2162+
}
2163+
21442164
initrd, err := os.Open(initrdPath)
21452165
if err != nil {
21462166
return err
@@ -2150,7 +2170,7 @@ func KexecPrepare(_ runtime.Sequence, data any) (runtime.TaskExecutionFunc, stri
21502170

21512171
cmdline := strings.TrimSpace(defaultEntry.Cmdline)
21522172

2153-
if err = unix.KexecFileLoad(int(kernel.Fd()), int(initrd.Fd()), cmdline, 0); err != nil {
2173+
if err = unix.KexecFileLoad(fd, int(initrd.Fd()), cmdline, 0); err != nil {
21542174
switch {
21552175
case errors.Is(err, unix.ENOSYS):
21562176
log.Printf("kexec support is disabled in the kernel")

internal/zboot/zboot.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
// Package zboot provides a function to extract the kernel from a Zboot image.
6+
package zboot
7+
8+
import (
9+
"bytes"
10+
"encoding/binary"
11+
"fmt"
12+
"io"
13+
"os"
14+
15+
"github.com/klauspost/compress/zstd"
16+
"golang.org/x/sys/unix"
17+
)
18+
19+
// fileCloser is an interface that wraps the Close method.
20+
type fileCloser interface {
21+
Close() error
22+
}
23+
24+
// Extract extracts the kernel from a Zboot image and returns a file descriptor of the extracted kernel.
25+
func Extract(kernel *os.File) (int, fileCloser, error) {
26+
// https://git.kernel.org/pub/scm/utils/kernel/kexec/kexec-tools.git/tree/include/kexec-pe-zboot.h
27+
var peZbootheaderData [28]byte
28+
29+
if _, err := io.ReadFull(kernel, peZbootheaderData[:]); err != nil {
30+
return 0, nil, err
31+
}
32+
33+
// https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/firmware/efi/libstub/zboot-header.S
34+
// https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/linux/pe.h#n42
35+
if !bytes.Equal(peZbootheaderData[:2], []byte("MZ")) {
36+
return 0, nil, fmt.Errorf("invalid PE Zboot header")
37+
}
38+
39+
// not a Zboot image, return
40+
if !bytes.Equal(peZbootheaderData[4:8], []byte("zimg")) {
41+
return int(kernel.Fd()), nil, nil
42+
}
43+
44+
payloadOffset := binary.LittleEndian.Uint32(peZbootheaderData[8:12])
45+
46+
payloadSize := binary.LittleEndian.Uint32(peZbootheaderData[12:16])
47+
48+
if _, err := kernel.Seek(int64(payloadOffset), io.SeekStart); err != nil {
49+
return 0, nil, fmt.Errorf("failed to seek to kernel zstd data from vmlinuz.efi: %w", err)
50+
}
51+
52+
z, err := zstd.NewReader(io.LimitReader(kernel, int64(payloadSize)))
53+
if err != nil {
54+
return 0, nil, fmt.Errorf("failed to create zstd reader: %w", err)
55+
}
56+
57+
defer z.Close()
58+
59+
fd, err := unix.MemfdCreate("vmlinux", 0)
60+
if err != nil {
61+
return 0, nil, fmt.Errorf("memfdCreate: %v", err)
62+
}
63+
64+
kernelMemfd := os.NewFile(uintptr(fd), "vmlinux")
65+
66+
if _, err := io.Copy(kernelMemfd, z); err != nil {
67+
kernelMemfd.Close() //nolint:errcheck
68+
69+
return 0, nil, fmt.Errorf("failed to copy zstd data to memfd: %w", err)
70+
}
71+
72+
if _, err := kernelMemfd.Seek(0, io.SeekStart); err != nil {
73+
kernelMemfd.Close() //nolint:errcheck
74+
75+
return 0, nil, fmt.Errorf("failed to seek to start of memfd: %w", err)
76+
}
77+
78+
return fd, kernelMemfd, nil
79+
}

0 commit comments

Comments
 (0)