diff --git a/pkg/visitors/helpers_test.go b/pkg/visitors/helpers_test.go index e8cb77cc..414ff663 100644 --- a/pkg/visitors/helpers_test.go +++ b/pkg/visitors/helpers_test.go @@ -16,6 +16,7 @@ import ( // This GUID exists somewhere in the OVMF image. var testGUID = guid.MustParse("DF1CCEF6-F301-4A63-9661-FC6030DCC880") var dxeCoreGUID = guid.MustParse("D6A2CB7F-6A18-4E2F-B43B-9920A733700A") +var microcodeRawGUID = guid.MustParse("1B45CC0A-156A-428A-AF62-49864DA0E6E6") func parseImage(t *testing.T) uefi.Firmware { image, err := os.ReadFile("../../integration/roms/OVMF.rom") diff --git a/pkg/visitors/replaceraw.go b/pkg/visitors/replaceraw.go new file mode 100644 index 00000000..bcbd34c1 --- /dev/null +++ b/pkg/visitors/replaceraw.go @@ -0,0 +1,91 @@ +// Copyright 2026 the LinuxBoot Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package visitors + +import ( + "errors" + "os" + + "github.com/linuxboot/fiano/pkg/uefi" +) + +// ReplaceRaw replaces EFI_SECTION_RAW sections with NewRaw for all files matching Predicate. +type ReplaceRaw struct { + // Input + Predicate func(f uefi.Firmware) bool + NewRaw []byte + + // Output + Matches []uefi.Firmware +} + +// Run wraps Visit and performs some setup and teardown tasks. +func (v *ReplaceRaw) Run(f uefi.Firmware) error { + // Run "find" to generate a list of matches to replace. + find := Find{ + Predicate: v.Predicate, + } + if err := find.Run(f); err != nil { + return err + } + + v.Matches = find.Matches + if len(find.Matches) == 0 { + return errors.New("no matches found for replacement") + } + if len(find.Matches) > 1 { + return errors.New("multiple matches found! There can be only one. Use find to list all matches") + } + + for _, m := range v.Matches { + if err := m.Apply(v); err != nil { + return err + } + } + return nil +} + +// Visit applies the ReplaceRaw visitor to any Firmware type. +func (v *ReplaceRaw) Visit(f uefi.Firmware) error { + switch f := f.(type) { + + case *uefi.File: + return f.ApplyChildren(v) + + case *uefi.Section: + if f.Header.Type == uefi.SectionTypeRaw { + f.SetBuf(v.NewRaw) + f.Encapsulated = nil // Raw sections have no encapsulated children + if err := f.GenSecHeader(); err != nil { + return err + } + } + return f.ApplyChildren(v) + + default: + // Must be applied to a File to have any effect. + return nil + } +} + +func init() { + RegisterCLI("replace_raw", "replace a raw section given a GUID or name and new file", 2, func(args []string) (uefi.Visitor, error) { + pred, err := FindFilePredicate(args[0]) + if err != nil { + return nil, err + } + + filename := args[1] + newRaw, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + + return &ReplaceRaw{ + Predicate: pred, + NewRaw: newRaw, + }, nil + }) +} diff --git a/pkg/visitors/replaceraw_test.go b/pkg/visitors/replaceraw_test.go new file mode 100644 index 00000000..583ae522 --- /dev/null +++ b/pkg/visitors/replaceraw_test.go @@ -0,0 +1,76 @@ +// Copyright 2026 the LinuxBoot Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package visitors + +import ( + "reflect" + "testing" + + "github.com/linuxboot/fiano/pkg/uefi" +) + +func TestReplaceRaw(t *testing.T) { + f := parseImage(t) + + replace := &ReplaceRaw{ + Predicate: FindFileGUIDPredicate(*microcodeRawGUID), + NewRaw: []byte("banana"), + } + if err := replace.Run(f); err != nil { + t.Fatal(err) + } + + if len(replace.Matches) != 1 { + t.Fatalf("got %d matches; expected 1", len(replace.Matches)) + } + + results := find(t, f, microcodeRawGUID) + if len(results) != 1 { + t.Fatalf("got %d matches; expected 1", len(results)) + } + want := []byte{0x0a, 0x00, 0x00, byte(uefi.SectionTypeRaw), 'b', 'a', 'n', 'a', 'n', 'a'} + file, ok := results[0].(*uefi.File) + if !ok { + t.Fatalf("did not match a file, got type :%T", file) + } + got := file.Sections[0].Buf() + if !reflect.DeepEqual(want, got) { + t.Fatalf("want %v; got %v", want, got) + } +} + +func TestReplaceRawErrors(t *testing.T) { + f := parseImage(t) + + var tests = []struct { + name string + newRaw []byte + match string + err string + }{ + {"No Matches", []byte("banana"), "no-match-string", + "no matches found for replacement"}, + {"Multiple Matches", []byte("banana"), ".*", + "multiple matches found! There can be only one. Use find to list all matches"}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + pred, err := FindFilePredicate(test.match) + if err != nil { + t.Fatal(err) + } + replace := &ReplaceRaw{ + Predicate: pred, + NewRaw: test.newRaw, + } + err = replace.Run(f) + if err == nil { + t.Fatalf("Expected Error (%v), got nil", test.err) + } else if err.Error() != test.err { + t.Fatalf("Mismatched Error: Expected %v, got %v", test.err, err.Error()) + } + }) + } +}