github.com/linuxboot/fiano@v1.2.0/pkg/visitors/repack.go (about) 1 // Copyright 2018 the LinuxBoot Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package visitors 6 7 import ( 8 "bytes" 9 "crypto/sha1" 10 "encoding/binary" 11 "errors" 12 "fmt" 13 "unsafe" 14 15 "github.com/linuxboot/fiano/pkg/compression" 16 "github.com/linuxboot/fiano/pkg/uefi" 17 ) 18 19 // Repack repacks a per file compressed FV into a singularly compressed nested FV 20 type Repack struct { 21 // Input 22 Predicate func(f uefi.Firmware) bool 23 24 // Matched File 25 FileMatch *uefi.File 26 } 27 28 // removeFileCompression goes through a newly created, nested firmware volume 29 // and removes the first level compressed section. 30 func removeFileCompression(nfv *uefi.FirmwareVolume) error { 31 for _, f := range nfv.Files { 32 newSectionList := []*uefi.Section{} 33 for _, s := range f.Sections { 34 if s.Header.Type != uefi.SectionTypeGUIDDefined { 35 // This doesn't have a compressed section 36 newSectionList = append(newSectionList, s) 37 continue 38 } 39 gdh := s.TypeSpecific.Header.(*uefi.SectionGUIDDefined) 40 if guid := gdh.GUID; guid != compression.LZMAGUID && guid != compression.LZMAX86GUID { 41 // This doesn't have a compressed section 42 newSectionList = append(newSectionList, s) 43 continue 44 } 45 // This is a compressed section we understand, remove it and add children directly to file. 46 for _, es := range s.Encapsulated { 47 child, ok := es.Value.(*uefi.Section) 48 if !ok { 49 // This should never happen, die 50 return fmt.Errorf("file %v has non sections inside a compressed section, discarding", f.Header.GUID) 51 } 52 newSectionList = append(newSectionList, child) 53 } 54 } 55 f.Sections = newSectionList 56 } 57 return nil 58 } 59 60 func createFirmwareVolume(pfv *uefi.FirmwareVolume) (*uefi.FirmwareVolume, error) { 61 nfv := &uefi.FirmwareVolume{} // new Firmware Volume 62 63 // Create new firmware volume that encloses all the old files. 64 // Set up volume header first. 65 nfv.FileSystemGUID = *uefi.FFS2 66 nfv.Signature = binary.LittleEndian.Uint32([]byte("_FVH")) 67 // Copy over attributes from parent. We may want to change this in the future. 68 // We don't need the alignment fields since we're nested, 69 // so we really only need the lower 16 bits. 70 nfv.Attributes = pfv.Attributes & 0x0000FFFF 71 nfv.Revision = pfv.Revision 72 // Copy and use parent's block size. We assume there's only one nonzero block entry 73 nfv.Blocks = make([]uefi.Block, 2) 74 if bLen := len(pfv.Blocks); bLen < 1 { 75 // According to the spec, should always be two blocks, one null to terminate the block list, but 76 // some bioses are not compliant. 77 return nil, fmt.Errorf("parent firmware volume block list is too short: need at least 1, got %v", 78 bLen) 79 } 80 if pfv.Blocks[0].Size == 0 { 81 return nil, errors.New("first parent firmware volume block has 0 block size, malformed parent block") 82 } 83 // We use the parent's block size 84 nfv.Blocks[0] = uefi.Block{Size: pfv.Blocks[0].Size} 85 nfv.Blocks[1] = uefi.Block{} 86 87 // Calculate the HeaderLen field 88 nfv.HeaderLen = uint16(uefi.FirmwareVolumeFixedHeaderSize + int(unsafe.Sizeof(uefi.Block{}))*len(nfv.Blocks)) 89 90 // Create firmware volume metadata 91 nfv.DataOffset = uint64(nfv.HeaderLen) // Since we don't have the extended header, HeaderLen is DataOffset. 92 nfv.Length = nfv.DataOffset 93 nfv.Resizable = true // This is a nested firmware volume, so we can resize it as needed. 94 95 // Generate binary header. 96 header := new(bytes.Buffer) 97 err := binary.Write(header, binary.LittleEndian, nfv.FirmwareVolumeFixedHeader) 98 if err != nil { 99 return nil, fmt.Errorf("unable to construct binary header of nested firmware volume: got %v", err) 100 } 101 for _, b := range nfv.Blocks { 102 err = binary.Write(header, binary.LittleEndian, b) 103 if err != nil { 104 return nil, fmt.Errorf("unable to construct binary header of nested firmware volume: got %v", err) 105 } 106 107 } 108 nfv.SetBuf(header.Bytes()) 109 // Copy out parent's files 110 nfv.Files = append([]*uefi.File{}, pfv.Files...) 111 112 return nfv, nil 113 } 114 115 func createVolumeImageFile(cs *uefi.Section) (*uefi.File, error) { 116 f := &uefi.File{} 117 118 f.Header.Type = uefi.FVFileTypeVolumeImage 119 f.Header.SetState(uefi.FileStateValid) 120 121 f.Sections = []*uefi.Section{cs} 122 123 // Call assemble to populate cs's buffer. then sha1 it for the guid. 124 a := &Assemble{} 125 if err := a.Run(cs); err != nil { 126 return nil, err 127 } 128 sum := sha1.Sum(cs.Buf()) 129 copy(f.Header.GUID[:], sum[:]) // GUIDs are smaller, so only 16 bytes are copied 130 131 return f, nil 132 } 133 134 func repackFV(fv *uefi.FirmwareVolume) error { 135 // fv should be the pointer to the enclosing firmware volume that needs to be repacked. 136 137 // Create new Firmware Volume. 138 // This copies out the parent list of files and assigns it to the new fv 139 nfv, err := createFirmwareVolume(fv) 140 if err != nil { 141 return err 142 } 143 144 // Remove per file compression 145 if err = removeFileCompression(nfv); err != nil { 146 return err 147 } 148 149 // Create new Volume Image section 150 vs, err := uefi.CreateSection(uefi.SectionTypeFirmwareVolumeImage, []byte{}, []uefi.Firmware{nfv}, nil) 151 if err != nil { 152 return err 153 } 154 155 // Create new compressed section 156 cs, err := uefi.CreateSection(uefi.SectionTypeGUIDDefined, []byte{}, []uefi.Firmware{vs}, &compression.LZMAGUID) 157 if err != nil { 158 return err 159 } 160 161 // Create new FV image file 162 file, err := createVolumeImageFile(cs) 163 if err != nil { 164 return err 165 } 166 167 // Set new file as the only firmware file in the original fv. 168 fv.Files = append([]*uefi.File{}, file) 169 return nil 170 } 171 172 // Run wraps Visit and performs some setup and teardown tasks. 173 func (v *Repack) Run(f uefi.Firmware) error { 174 // Check that fv being repacked isn't already nested. 175 // First run "find" to generate a position to insert into. 176 find := Find{ 177 Predicate: v.Predicate, 178 } 179 if err := find.Run(f); err != nil { 180 return err 181 } 182 183 if numMatch := len(find.Matches); numMatch > 1 { 184 return fmt.Errorf("more than one match, only one match allowed! got %v", find.Matches) 185 } else if numMatch == 0 { 186 return errors.New("no matches found") 187 } 188 189 // Find should only match a file or a firmware volume. If it's an FV, we can 190 // edit the FV directly. 191 if fvMatch, ok := find.Matches[0].(*uefi.FirmwareVolume); ok { 192 // Call repack function. 193 return repackFV(fvMatch) 194 } 195 var ok bool 196 if v.FileMatch, ok = find.Matches[0].(*uefi.File); !ok { 197 return fmt.Errorf("match was not a file or a firmware volume: got %T, unable to insert", find.Matches[0]) 198 } 199 // Match is a file, apply visitor. 200 if err := f.Apply(v); err != nil { 201 return err 202 } 203 204 // Assemble the tree just to make sure things are right. 205 a := &Assemble{} 206 return a.Run(f) 207 } 208 209 // Visit applies the Repack visitor to any Firmware type. 210 func (v *Repack) Visit(f uefi.Firmware) error { 211 switch f := f.(type) { 212 case *uefi.FirmwareVolume: 213 for i := 0; i < len(f.Files); i++ { 214 if f.Files[i] == v.FileMatch { 215 // call repack function. 216 return repackFV(f) 217 } 218 } 219 } 220 221 return f.ApplyChildren(v) 222 } 223 224 func init() { 225 RegisterCLI("repack", 226 "repack a per file compressed fv to a nested compressed fv", 1, 227 func(args []string) (uefi.Visitor, error) { 228 pred, err := FindFileFVPredicate(args[0]) 229 if err != nil { 230 return nil, err 231 } 232 233 // Repack File. 234 return &Repack{ 235 Predicate: pred, 236 }, nil 237 }) 238 }