From 11c2c1a9fd46d33d2e4b21901a3a48fdda1e31a4 Mon Sep 17 00:00:00 2001 From: Gan Shun Date: Fri, 5 Nov 2021 10:25:18 -0400 Subject: [PATCH] WIP: create-ffs (#239) create-ffs: A very rudimentary create-ffs command Signed-off-by: Gan Shun Lim --- .gitignore | 1 + cmds/.gitignore | 4 + cmds/create-ffs/create-ffs.go | 212 +++++++++++++++++++++++++++++ cmds/create-ffs/create-ffs_test.go | 95 +++++++++++++ pkg/uefi/file.go | 13 +- pkg/uefi/section.go | 2 +- pkg/visitors/assemble.go | 6 +- 7 files changed, 328 insertions(+), 5 deletions(-) create mode 100644 cmds/.gitignore create mode 100644 cmds/create-ffs/create-ffs.go create mode 100644 cmds/create-ffs/create-ffs_test.go diff --git a/.gitignore b/.gitignore index e85b22d8..dc97de35 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ cmds/futk/futk cmds/glzma/glzma cmds/guid2english/guid2english cmds/utk/utk +cmds/create-ffs/create-ffs cmds/futk/futk cmds/fspinfo/fspinfo .vimrc diff --git a/cmds/.gitignore b/cmds/.gitignore new file mode 100644 index 00000000..9a0160fd --- /dev/null +++ b/cmds/.gitignore @@ -0,0 +1,4 @@ +# Ignore binaries (files without extensions) +* +!*.* +!*/ diff --git a/cmds/create-ffs/create-ffs.go b/cmds/create-ffs/create-ffs.go new file mode 100644 index 00000000..5a1b01ba --- /dev/null +++ b/cmds/create-ffs/create-ffs.go @@ -0,0 +1,212 @@ +// Copyright 2019 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 main + +import ( + "crypto/sha1" + "errors" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "strings" + + "github.com/linuxboot/fiano/pkg/guid" + "github.com/linuxboot/fiano/pkg/uefi" + "github.com/linuxboot/fiano/pkg/visitors" +) + +var ( + debug = flag.Bool("d", false, "Enable debug prints") + outfile = flag.String("o", "", "output file, default is stdout") + name = flag.String("name", "", "Name to include in UI section") + filetype = flag.String("type", "DRIVER", "UEFI filetype") + version = flag.String("version", "1.0", "File version") + guidString = flag.String("guid", "", "File GUID") + depex = flag.String("depex", "", "Space or comma separated protocol guid dependencies or TRUE") + compress = flag.Bool("compress", false, "Wrap section data in a compressed section") + auto = flag.Bool("auto", false, "Attempt to determine section types from file extensions") + + printf = func(string, ...interface{}) {} +) + +const ( + usageString = "Usage: create-ffs [flags] file.efi" +) + +func createDepExes(deps string) ([]uefi.DepExOp, error) { + var err error + ops := []uefi.DepExOp{} + + if strings.ToUpper(deps) == "TRUE" { + // Create just "TRUE" and "END" for now, but this feels unnecessary to me. + ops = append(ops, uefi.DepExOp{OpCode: "TRUE"}) + ops = append(ops, uefi.DepExOp{OpCode: "END"}) + return ops, nil + } + + // we expect a space or comma separated list of GUIDs, split by both. + guidSpace := strings.Split(deps, " ") + guids := []string{} + for _, g := range guidSpace { + guids = append(guids, strings.Split(g, ",")...) + } + + for _, guidStr := range guids { + var g *guid.GUID + + if g, err = guid.Parse(guidStr); err != nil { + return nil, err + } + printf("depex guid requested: %v", *g) + ops = append(ops, uefi.DepExOp{OpCode: "PUSH", GUID: g}) + } + + // Append an "AND" for n-1 pushes. + for i := 1; i < len(guids); i++ { + ops = append(ops, uefi.DepExOp{OpCode: "AND"}) + } + + ops = append(ops, uefi.DepExOp{OpCode: "END"}) + return ops, nil +} + +func parseFlags() (fType uefi.FVFileType, fGUID *guid.GUID, depOps []uefi.DepExOp, err error) { + var ok bool + + if *debug { + printf = log.Printf + } + + // Check filetypes + fType, ok = uefi.NamesToFileType[*filetype] + if !ok { + validTypes := []string{} + for k := range uefi.NamesToFileType { + validTypes = append(validTypes, k) + } + err = fmt.Errorf("unable to get EFI File type, got %v, expected values in\n{%v}", + *filetype, strings.Join(validTypes, ", ")) + return + } + + if *guidString != "" { + fGUID, err = guid.Parse(*guidString) + if err != nil { + return + } + } else if *name != "" { + // We sha1 the name to get a reproducible GUID. + fGUID = &guid.GUID{} + sum := sha1.Sum([]byte(*name)) + copy(fGUID[:], sum[:guid.Size]) + } else { + err = errors.New("no GUID or name provided, please provide at least one") + return + } + + if *outfile == "" { + err = errors.New("we don't currently support dumping to stdout, please specify an output file") + return + } + + if *depex != "" { + depOps, err = createDepExes(*depex) + if err != nil { + err = fmt.Errorf("can't parse depex guids, got %v", err) + } + } + + return +} + +func usage() { + log.Print(usageString) + flag.PrintDefaults() + os.Exit(1) +} + +func main() { + var fType uefi.FVFileType + var fGUID *guid.GUID + var depOps []uefi.DepExOp + var err error + + flag.Parse() + if fType, fGUID, depOps, err = parseFlags(); err != nil { + log.Fatal(err) + } + + if flag.NArg() != 1 { + usage() + } + + secData, err := ioutil.ReadFile(flag.Arg(0)) + if err != nil { + log.Fatal(err) + } + + printf("type requested: %v", fType) + printf("name requested: %v", *name) + printf("version requested: %v", *version) + printf("guid: %v", fGUID) + + secType := uefi.SectionTypeRaw + switch fType { + case uefi.FVFileTypeApplication, uefi.FVFileTypeCombinedSMMDXE, + uefi.FVFileTypeCombinedPEIMDriver, uefi.FVFileTypeDriver: + secType = uefi.SectionTypePE32 + } + + file := &uefi.File{} + file.Header.Type = fType + file.Header.GUID = *fGUID + file.Header.State = 0xF8 + file.Header.Attributes = 0x40 + + mainSection := &uefi.Section{} + mainSection.SetType(secType) + mainSection.SetBuf(secData) + mainSection.GenSecHeader() + file.Sections = append(file.Sections, mainSection) + printf("selected section type: %v", mainSection.Header.Type) + + if *name != "" { + s := &uefi.Section{} + s.SetType(uefi.SectionTypeUserInterface) + s.Name = *name + file.Sections = append(file.Sections, s) + } + + if *version != "" { + s := &uefi.Section{} + s.SetType(uefi.SectionTypeVersion) + s.Version = *version + file.Sections = append(file.Sections, s) + } + + if *depex != "" { + s := &uefi.Section{} + s.SetType(uefi.SectionTypeDXEDepEx) + s.DepEx = depOps + file.Sections = append(file.Sections, s) + } + + save := &visitors.Save{DirPath: *outfile} + + err = file.Apply(save) + if err != nil { + log.Fatal(err) + } + + if *debug { + // Dump file json for checking + jsonv := &visitors.JSON{W: os.Stdout} + if err = file.Apply(jsonv); err != nil { + log.Fatal(err) + } + } +} diff --git a/cmds/create-ffs/create-ffs_test.go b/cmds/create-ffs/create-ffs_test.go new file mode 100644 index 00000000..8e6f3c0f --- /dev/null +++ b/cmds/create-ffs/create-ffs_test.go @@ -0,0 +1,95 @@ +// Copyright 2019 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 main + +import ( + "fmt" + "testing" + + "github.com/linuxboot/fiano/pkg/guid" + "github.com/linuxboot/fiano/pkg/uefi" +) + +func compareErrors(expected string, err error) error { + if expected != "" { + if err == nil { + return fmt.Errorf("error was not returned, expected %v", expected) + } + if expected != err.Error() { + return fmt.Errorf("mismatched error returned, expected \n%v\n got \n%v", + expected, err.Error()) + } + } else if err != nil { + return fmt.Errorf("error was not expected, got %v", err) + } + return nil +} + +func compareOps(expectedOps []uefi.DepExOp, ops []uefi.DepExOp) error { + if expectedOps != nil { + if ops == nil { + return fmt.Errorf("expected ops: %v, got nil", expectedOps) + } + if elen, olen := len(expectedOps), len(ops); elen != olen { + return fmt.Errorf("different lenghts of depexes expected \n%v\n got \n%v", + elen, olen) + } + for i := range expectedOps { + if expectedOps[i].OpCode != ops[i].OpCode { + return fmt.Errorf("different opcodes! expected %v, got %v", + expectedOps[i].OpCode, ops[i].OpCode) + } + if expectedOps[i].GUID != nil { + if ops[i].GUID == nil { + return fmt.Errorf("expected GUID %v, got nil", + *expectedOps[i].GUID) + } + if *expectedOps[i].GUID != *ops[i].GUID { + return fmt.Errorf("mismatched GUID, expected %v, got %v", + *expectedOps[i].GUID, *ops[i].GUID) + } + } else if ops[i].GUID != nil { + return fmt.Errorf("expected no GUIDs, got %v", *ops[i].GUID) + } + } + } else if ops != nil { + return fmt.Errorf("expected no ops, got %v", ops) + } + return nil +} + +func TestCreateDepExes(t *testing.T) { + var tests = []struct { + name string + input string + ops []uefi.DepExOp + msg string + }{ + {"trueDepEx", "TRUE", []uefi.DepExOp{{OpCode: "TRUE"}, {OpCode: "END"}}, ""}, + {"pushone", "01234567-89AB-CDEF-0123-456789ABCDEF", + []uefi.DepExOp{ + {OpCode: "PUSH", GUID: guid.MustParse("01234567-89AB-CDEF-0123-456789ABCDEF")}, + {OpCode: "END"}}, ""}, + {"pushtwo", "01234567-89AB-CDEF-0123-456789ABCDEF 01234567-89AB-CDEF-0123-456789ABCDEF", + []uefi.DepExOp{ + {OpCode: "PUSH", GUID: guid.MustParse("01234567-89AB-CDEF-0123-456789ABCDEF")}, + {OpCode: "PUSH", GUID: guid.MustParse("01234567-89AB-CDEF-0123-456789ABCDEF")}, + {OpCode: "AND"}, + {OpCode: "END"}}, ""}, + {"badGUID", "ABC", nil, "guid string not correct, need string of the format \n01234567-89AB-CDEF-0123-456789ABCDEF" + + "\n, got \nABC"}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + outputs, err := createDepExes(test.input) + if err = compareErrors(test.msg, err); err != nil { + t.Fatal(err) + } + if err = compareOps(test.ops, outputs); err != nil { + t.Fatal(err) + } + }) + } +} diff --git a/pkg/uefi/file.go b/pkg/uefi/file.go index 16daf91e..91965eb6 100644 --- a/pkg/uefi/file.go +++ b/pkg/uefi/file.go @@ -8,6 +8,7 @@ import ( "bytes" "encoding/binary" "fmt" + "strings" "github.com/linuxboot/fiano/pkg/guid" "github.com/linuxboot/fiano/pkg/log" @@ -83,6 +84,16 @@ var fileTypeNames = map[FVFileType]string{ FVFileTypeSMMCoreStandalone: "EFI_FV_FILETYPE_MM_CORE_STANDALONE", } +// NamesToFileType maps from common file type strings to the actual type. +var NamesToFileType map[string]FVFileType + +func init() { + NamesToFileType = make(map[string]FVFileType) + for k, v := range fileTypeNames { + NamesToFileType[strings.TrimPrefix(v, "EFI_FV_FILETYPE_")] = k + } +} + // String creates a string representation for the file type. func (f FVFileType) String() string { switch { @@ -111,7 +122,7 @@ var ( // FileAlignments specifies the correct alignments based on the field in the file header. var fileAlignments = []uint64{ - // These alignments not computable, we have to look them up. + // These alignments are not computable, we have to look them up. 1, 16, 128, diff --git a/pkg/uefi/section.go b/pkg/uefi/section.go index 47c84120..65748104 100644 --- a/pkg/uefi/section.go +++ b/pkg/uefi/section.go @@ -66,7 +66,7 @@ var sectionTypeNames = map[SectionType]string{ SectionMMDepEx: "EFI_SECTION_MM_DEPEX", } -// String creates a string representation for the file type. +// String creates a string representation for the section type. func (s SectionType) String() string { if t, ok := sectionTypeNames[s]; ok { return t diff --git a/pkg/visitors/assemble.go b/pkg/visitors/assemble.go index a2015d5f..3ca15eb8 100644 --- a/pkg/visitors/assemble.go +++ b/pkg/visitors/assemble.go @@ -192,9 +192,9 @@ func (v *Assemble) Visit(f uefi.Firmware) error { } // Otherwise, we reconstruct the entire file from the sections and the - // file header using data from the JSON. This means that some JSON values - // are now respected, including GUID changes. However file lengths and - // checksums will be recalculated. + // file header using data from the JSON/existing header struct. This means + // that some JSON values are now respected, including GUID changes. + // However file lengths and checksums will be recalculated. // Assemble all sections so we know the final file size. We need to do this // to know if we need to use the extended header.