fix: build CPU ucode correctly for early loader

Closes #7729

This follows the steps described in
https://www.kernel.org/doc/html/v6.1/x86/microcode.html#early-load-microcode

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
Andrey Smirnov 2023-09-18 12:45:07 +04:00
parent c5bd0ac5cf
commit e3b4940588
No known key found for this signature in database
GPG Key ID: FE042E3D4085A811
5 changed files with 185 additions and 27 deletions

View File

@ -15,6 +15,20 @@ import (
"github.com/siderolabs/talos/pkg/machinery/constants"
)
// List of globs and destinations for early CPU ucode.
//
// See https://www.kernel.org/doc/html/v6.1/x86/microcode.html#early-load-microcode.
//
// We need to repackage the ucode blobs matching the glob into the destination concatenating
// them all together.
// The resulting blobs should be placed into uncompressed cpio archive prepended to the normal (compressed) initramfs.
var earlyCPUUcode = []struct {
glob, dst string
}{
{"/lib/firmware/intel-ucode/*", "kernel/x86/microcode/GenuineIntel.bin"},
{"/lib/firmware/amd-ucode/microcode_amd*.bin", "kernel/x86/microcode/AuthenticAMD.bin"},
}
// List of paths to be moved to the future initramfs.
var initramfsPaths = []string{
constants.FirmwarePath,
@ -23,7 +37,12 @@ var initramfsPaths = []string{
// Compress builds the squashfs image in the specified destination folder.
//
// Components which should be placed to the initramfs are moved to the initramfsPath.
// Ucode components are moved into a separate designated location.
func (ext *Extension) Compress(squashPath, initramfsPath string) (string, error) {
if err := ext.handleUcode(initramfsPath); err != nil {
return "", err
}
for _, path := range initramfsPaths {
if _, err := os.Stat(filepath.Join(ext.rootfsPath, path)); err == nil {
if err = moveFiles(filepath.Join(ext.rootfsPath, path), filepath.Join(initramfsPath, path)); err != nil {
@ -40,6 +59,62 @@ func (ext *Extension) Compress(squashPath, initramfsPath string) (string, error)
return squashPath, cmd.Run()
}
func appendBlob(dst io.Writer, srcPath string) error {
src, err := os.Open(srcPath)
if err != nil {
return err
}
defer src.Close() //nolint:errcheck
if _, err = io.Copy(dst, src); err != nil {
return err
}
if err = src.Close(); err != nil {
return err
}
return os.Remove(srcPath)
}
func (ext *Extension) handleUcode(initramfsPath string) error {
for _, ucode := range earlyCPUUcode {
matches, err := filepath.Glob(filepath.Join(ext.rootfsPath, ucode.glob))
if err != nil {
return err
}
if len(matches) == 0 {
continue
}
// if some ucode is found, append it to the blob in the initramfs
if err = os.MkdirAll(filepath.Dir(filepath.Join(initramfsPath, ucode.dst)), 0o755); err != nil {
return err
}
dst, err := os.OpenFile(filepath.Join(initramfsPath, ucode.dst), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o644)
if err != nil {
return err
}
defer dst.Close() //nolint:errcheck
for _, match := range matches {
if err = appendBlob(dst, match); err != nil {
return err
}
}
if err = dst.Close(); err != nil {
return err
}
}
return nil
}
func moveFiles(srcPath, dstPath string) error {
return handleFilesOp(srcPath, dstPath, os.Remove)
}

View File

@ -39,7 +39,7 @@ func (ext *Extension) KernelModuleDirectory() string {
// GenerateKernelModuleDependencyTreeExtension generates a kernel module dependency tree extension.
//
//nolint:gocyclo
func GenerateKernelModuleDependencyTreeExtension(extensionsPathWithKernelModules []string, initramfsPath string, printFunc func(format string, v ...any)) (*Extension, error) {
func GenerateKernelModuleDependencyTreeExtension(extensionPathsWithKernelModules []string, initramfsPath, extensionTreePath string, printFunc func(format string, v ...any)) (*Extension, error) {
printFunc("preparing to run depmod to generate kernel modules dependency tree")
tempDir, err := os.MkdirTemp("", "ext-modules")
@ -93,7 +93,7 @@ func GenerateKernelModuleDependencyTreeExtension(extensionsPathWithKernelModules
kernelVersionPath := contents[0].Name()
// copy to the same location modules from all extensions
for _, path := range extensionsPathWithKernelModules {
for _, path := range extensionPathsWithKernelModules {
if err = copyFiles(filepath.Join(path, kernelVersionPath), filepath.Join(rootfsKernelModulesPath, kernelVersionPath)); err != nil {
return nil, fmt.Errorf("copying kernel modules from %s failed: %w", path, err)
}
@ -105,14 +105,11 @@ func GenerateKernelModuleDependencyTreeExtension(extensionsPathWithKernelModules
return nil, fmt.Errorf("error running depmod: %w", err)
}
// we want this temp directory to be present until the extension is compressed later on, so not removing it here
kernelModulesDependencyTreeStagingDir, err := os.MkdirTemp("", "module-deps")
if err != nil {
return nil, err
}
// we want the extension to survive this function, so not storing in a temporary directory
kernelModulesDependencyTreeStagingDir := filepath.Join(extensionTreePath, "modules.dep")
// we want to make sure the root directory has the right permissions.
if err := os.Chmod(kernelModulesDependencyTreeStagingDir, 0o755); err != nil {
if err := os.MkdirAll(kernelModulesDependencyTreeStagingDir, 0o755); err != nil {
return nil, err
}

View File

@ -10,6 +10,7 @@ import (
"io"
"os"
"path/filepath"
"strings"
"github.com/siderolabs/talos/internal/pkg/extensions"
)
@ -26,19 +27,28 @@ func findExtensionsWithKernelModules(extensions []*extensions.Extension) []strin
return modulesPath
}
func buildContents(path string) (io.Reader, error) {
var listing bytes.Buffer
// buildInitramfsContents builds a list of files to be included into initramfs directly, bypassing extensions squashfs.
//
// Two listings are returned:
// - uncompressedListing is a list of files that should be included into initramfs uncompressed prepended as a first section
// - compressedListing is a list of files that should be included into initramfs compressed.
func buildInitramfsContents(path string) (compressedListing, uncompressedListing []byte, err error) {
var compressedBuffer, uncompressedBuffer bytes.Buffer
if err := buildContentsRecursive(path, "", &listing); err != nil {
return nil, err
if err := buildInitramfsContentsRecursive(path, "", &compressedBuffer, &uncompressedBuffer); err != nil {
return nil, nil, err
}
return &listing, nil
return compressedBuffer.Bytes(), uncompressedBuffer.Bytes(), nil
}
func buildContentsRecursive(basePath, path string, w io.Writer) error {
func buildInitramfsContentsRecursive(basePath, path string, compressedW, uncompressedW io.Writer) error {
if path != "" {
fmt.Fprintf(w, "%s\n", path)
if path == "kernel" || strings.HasPrefix(path, "kernel/") {
fmt.Fprintf(uncompressedW, "%s\n", path)
} else {
fmt.Fprintf(compressedW, "%s\n", path)
}
}
st, err := os.Stat(filepath.Join(basePath, path))
@ -56,7 +66,7 @@ func buildContentsRecursive(basePath, path string, w io.Writer) error {
}
for _, item := range contents {
if err = buildContentsRecursive(basePath, filepath.Join(path, item.Name()), w); err != nil {
if err = buildInitramfsContentsRecursive(basePath, filepath.Join(path, item.Name()), compressedW, uncompressedW); err != nil {
return err
}
}

View File

@ -21,7 +21,7 @@ type Builder struct {
InitramfsPath string
// Architecture of the initramfs.
Arch string
// ExtensionTreePath is a path to the extracted exension tree.
// ExtensionTreePath is a path to the extracted extension tree.
ExtensionTreePath string
// Printf is used for logging.
Printf func(format string, v ...any)
@ -46,10 +46,10 @@ func (builder *Builder) Build() error {
return err
}
extensionsPathWithKernelModules := findExtensionsWithKernelModules(extensionsList)
extensionPathsWithKernelModules := findExtensionsWithKernelModules(extensionsList)
if len(extensionsPathWithKernelModules) > 0 {
kernelModuleDepExtension, genErr := extensions.GenerateKernelModuleDependencyTreeExtension(extensionsPathWithKernelModules, builder.InitramfsPath, builder.Printf)
if len(extensionPathsWithKernelModules) > 0 {
kernelModuleDepExtension, genErr := extensions.GenerateKernelModuleDependencyTreeExtension(extensionPathsWithKernelModules, builder.InitramfsPath, builder.ExtensionTreePath, builder.Printf)
if genErr != nil {
return genErr
}

View File

@ -5,21 +5,43 @@
package extensions
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
)
// rebuildInitramfs rebuilds finalized initramfs with extensions.
//
// If uncompressedListing is not empty, contents will be prepended to the initramfs uncompressed.
// Contents from compressedListing will be appended to the initramfs compressed (xz) as a second block.
// Original initramfs.xz contents will stay without changes.
func (builder *Builder) rebuildInitramfs(tempDir string) error {
compressedListing, uncompressedListing, err := buildInitramfsContents(tempDir)
if err != nil {
return err
}
if len(uncompressedListing) > 0 {
if err = builder.prependUncompressedInitramfs(tempDir, uncompressedListing); err != nil {
return fmt.Errorf("error prepending uncompressed initramfs: %w", err)
}
}
if err = builder.appendCompressedInitramfs(tempDir, compressedListing); err != nil {
return fmt.Errorf("error appending compressed initramfs: %w", err)
}
return nil
}
func (builder *Builder) appendCompressedInitramfs(tempDir string, compressedListing []byte) error {
builder.Printf("creating system extensions initramfs archive and compressing it")
// the code below runs the equivalent of:
// find $tempDir -print | cpio -H newc --create --reproducible | xz -v -C crc32 -0 -e -T 0 -z
listing, err := buildContents(tempDir)
if err != nil {
return err
}
pipeR, pipeW, err := os.Pipe()
if err != nil {
return err
@ -29,9 +51,9 @@ func (builder *Builder) rebuildInitramfs(tempDir string) error {
defer pipeW.Close() //nolint:errcheck
// build cpio image which contains .sqsh images and extensions.yaml
cmd1 := exec.Command("cpio", "-H", "newc", "--create", "--reproducible", "--quiet")
cmd1 := exec.Command("cpio", "-H", "newc", "--create", "--reproducible", "--quiet", "-R", "+0:+0")
cmd1.Dir = tempDir
cmd1.Stdin = listing
cmd1.Stdin = bytes.NewReader(compressedListing)
cmd1.Stdout = pipeW
cmd1.Stderr = os.Stderr
@ -83,3 +105,57 @@ func (builder *Builder) rebuildInitramfs(tempDir string) error {
return destination.Sync()
}
func (builder *Builder) prependUncompressedInitramfs(tempDir string, uncompressedListing []byte) error {
builder.Printf("creating uncompressed initramfs archive")
// the code below runs the equivalent of:
// mv initramfs.xz initramfs.xz-old
// find $tempDir -print | cpio -H newc --create --reproducible > initramfs.xz
// cat initramfs.xz-old >> initramfs.xz
// rm initramfs.xz-old
initramfsOld := builder.InitramfsPath + "-old"
if err := os.Rename(builder.InitramfsPath, initramfsOld); err != nil {
return err
}
destination, err := os.OpenFile(builder.InitramfsPath, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer destination.Close() //nolint:errcheck
cmd := exec.Command("cpio", "-H", "newc", "--create", "--reproducible", "--quiet", "-R", "+0:+0")
cmd.Dir = tempDir
cmd.Stdin = bytes.NewReader(uncompressedListing)
cmd.Stdout = destination
cmd.Stderr = os.Stderr
if err = cmd.Run(); err != nil {
return err
}
old, err := os.Open(initramfsOld)
if err != nil {
return err
}
defer old.Close() //nolint:errcheck
if _, err = io.Copy(destination, old); err != nil {
return err
}
if err = destination.Close(); err != nil {
return err
}
if err = old.Close(); err != nil {
return err
}
return os.Remove(initramfsOld)
}