github.com/linuxboot/fiano@v1.2.0/pkg/visitors/validate.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 "encoding/binary" 9 "errors" 10 "fmt" 11 "io" 12 "os" 13 14 "github.com/linuxboot/fiano/pkg/uefi" 15 ) 16 17 // Validate performs extra checks on the firmware image. 18 type Validate struct { 19 // An optional Writer for writing errors when validation is complete. 20 // When the writer it set, Run will also call os.Exit(1) upon finding 21 // an error. 22 W io.Writer 23 24 // List of validation errors. 25 Errors []error 26 } 27 28 // Run wraps Visit and performs some setup and teardown tasks. 29 func (v *Validate) Run(f uefi.Firmware) error { 30 if err := f.Apply(v); err != nil { 31 return err 32 } 33 34 if v.W != nil && len(v.Errors) != 0 { 35 for _, e := range v.Errors { 36 fmt.Println(e) 37 } 38 os.Exit(1) 39 } 40 return nil 41 } 42 43 // Visit applies the Validate visitor to any Firmware type. 44 func (v *Validate) Visit(f uefi.Firmware) error { 45 // TODO: add more verification where needed 46 switch f := f.(type) { 47 case *uefi.FlashImage: 48 _, err := f.FindSignature() 49 if err != nil { 50 v.Errors = append(v.Errors, err) 51 } 52 53 case *uefi.FlashDescriptor: 54 d := f.DescriptorMap 55 if d.MasterBase > uefi.FlashDescriptorMapMaxBase { 56 v.Errors = append(v.Errors, fmt.Errorf("MasterBase too large: expected %v bytes, got %v", 57 uefi.FlashDescriptorMapMaxBase, 58 d.MasterBase, 59 )) 60 } 61 if d.RegionBase > uefi.FlashDescriptorMapMaxBase { 62 v.Errors = append(v.Errors, fmt.Errorf("RegionBase too large: expected %v bytes, got %v", 63 uefi.FlashDescriptorMapMaxBase, 64 d.RegionBase, 65 )) 66 } 67 if d.MasterBase > uefi.FlashDescriptorMapMaxBase { 68 v.Errors = append(v.Errors, fmt.Errorf("ComponentBase too large: expected %v bytes, got %v", 69 uefi.FlashDescriptorMapMaxBase, 70 d.MasterBase, 71 )) 72 } 73 if d.MasterBase == d.RegionBase { 74 v.Errors = append(v.Errors, fmt.Errorf("MasterBase must be different from RegionBase: both are at 0x%x", 75 d.MasterBase, 76 )) 77 } 78 if d.MasterBase == d.ComponentBase { 79 v.Errors = append(v.Errors, fmt.Errorf("MasterBase must be different from ComponentBase: both are at 0x%x", 80 d.MasterBase, 81 )) 82 } 83 if d.RegionBase == d.ComponentBase { 84 v.Errors = append(v.Errors, fmt.Errorf("RegionBase must be different from ComponentBase: both are at 0x%x", 85 d.RegionBase, 86 )) 87 } 88 89 case *uefi.FirmwareVolume: 90 // Check for min length 91 fvlen := uint64(len(f.Buf())) 92 // We need this check in case HeaderLen doesn't exist, and bail out early 93 if fvlen < uefi.FirmwareVolumeMinSize { 94 v.Errors = append(v.Errors, fmt.Errorf("length too small!, buffer is only %#x bytes long", fvlen)) 95 break 96 } 97 // Check header length 98 if f.HeaderLen < uefi.FirmwareVolumeMinSize { 99 v.Errors = append(v.Errors, fmt.Errorf("header length too small, got: %#x", f.HeaderLen)) 100 break 101 } 102 // Check for full header and bail out if its not fully formed. 103 if fvlen < uint64(f.HeaderLen) { 104 v.Errors = append(v.Errors, fmt.Errorf("buffer smaller than header!, header is %#x bytes, buffer is %#x bytes", 105 f.HeaderLen, fvlen)) 106 break 107 } 108 // Do we want to fail in this case? maybe not. 109 if uefi.FVGUIDs[f.FileSystemGUID] == "" { 110 v.Errors = append(v.Errors, fmt.Errorf("unknown FV type! Guid was %v", f.FileSystemGUID)) 111 } 112 // UEFI PI spec says version should always be 2 113 if f.Revision != 2 { 114 v.Errors = append(v.Errors, fmt.Errorf("revision should be 2, was %v", f.Revision)) 115 } 116 // Check Signature 117 fvSigInt := binary.LittleEndian.Uint32([]byte("_FVH")) 118 if f.Signature != fvSigInt { 119 v.Errors = append(v.Errors, fmt.Errorf("signature was not _FVH, got: %#08x", f.Signature)) 120 } 121 // Check length 122 if f.Length != fvlen { 123 v.Errors = append(v.Errors, fmt.Errorf("length mismatch!, header has %#x, buffer is %#x bytes long", f.Length, fvlen)) 124 } 125 // Check checksum 126 sum, err := uefi.Checksum16(f.Buf()[:f.HeaderLen]) // TODO: use the Header() function which does not exist yet 127 if err != nil { 128 v.Errors = append(v.Errors, fmt.Errorf("unable to checksum FV header: %v", err)) 129 } else if sum != 0 { 130 v.Errors = append(v.Errors, fmt.Errorf("header did not sum to 0, got: %#x", sum)) 131 } 132 133 case *uefi.File: 134 buflen := uint64(len(f.Buf())) 135 blankSize := [3]uint8{0xFF, 0xFF, 0xFF} 136 if buflen < uefi.FileHeaderMinLength { 137 v.Errors = append(v.Errors, fmt.Errorf("file length too small!, buffer is only %#x bytes long", buflen)) 138 break 139 } 140 141 // Size Checks 142 fh := &f.Header 143 if fh.Size == blankSize { 144 if buflen < uefi.FileHeaderExtMinLength { 145 v.Errors = append(v.Errors, fmt.Errorf("file %v length too small!, buffer is only %#x bytes long for extended header", 146 fh.GUID, buflen)) 147 break 148 } 149 if !fh.Attributes.IsLarge() { 150 v.Errors = append(v.Errors, fmt.Errorf("file %v using extended header, but large attribute is not set", 151 fh.GUID)) 152 break 153 } 154 } else if uefi.Read3Size(f.Header.Size) != fh.ExtendedSize { 155 v.Errors = append(v.Errors, fmt.Errorf("file %v size not copied into extendedsize", 156 fh.GUID)) 157 break 158 } 159 if buflen != fh.ExtendedSize { 160 v.Errors = append(v.Errors, fmt.Errorf("file %v size mismatch! Size is %#x, buf length is %#x", 161 fh.GUID, fh.ExtendedSize, buflen)) 162 break 163 } 164 165 // Header Checksums 166 if sum := f.ChecksumHeader(); sum != 0 { 167 v.Errors = append(v.Errors, fmt.Errorf("file %v header checksum failure! sum was %v", 168 fh.GUID, sum)) 169 } 170 171 // Body Checksum 172 if !fh.Attributes.HasChecksum() && fh.Checksum.File != uefi.EmptyBodyChecksum { 173 v.Errors = append(v.Errors, fmt.Errorf("file %v body checksum failure! Attribute was not set, but sum was %v instead of %v", 174 fh.GUID, fh.Checksum.File, uefi.EmptyBodyChecksum)) 175 } else if fh.Attributes.HasChecksum() { 176 headerSize := uefi.FileHeaderMinLength 177 if fh.Attributes.IsLarge() { 178 headerSize = uefi.FileHeaderExtMinLength 179 } 180 if sum := uefi.Checksum8(f.Buf()[headerSize:]); sum != 0 { // TODO: use the Payload function which does not exist yet 181 v.Errors = append(v.Errors, fmt.Errorf("file %v body checksum failure! sum was %v", 182 fh.GUID, sum)) 183 } 184 } 185 186 case *uefi.Section: 187 buflen := uint32(len(f.Buf())) 188 blankSize := [3]uint8{0xFF, 0xFF, 0xFF} 189 190 // Size Checks 191 sh := &f.Header 192 if sh.Size == blankSize { 193 if buflen < uefi.SectionExtMinLength { 194 v.Errors = append(v.Errors, fmt.Errorf("section length too small!, buffer is only %#x bytes long for extended header", 195 buflen)) 196 break 197 } 198 } else if uint32(uefi.Read3Size(f.Header.Size)) != sh.ExtendedSize { 199 v.Errors = append(v.Errors, errors.New("section size not copied into extendedsize")) 200 break 201 } 202 if buflen != sh.ExtendedSize { 203 v.Errors = append(v.Errors, fmt.Errorf("section size mismatch! Size is %#x, buf length is %#x", 204 sh.ExtendedSize, buflen)) 205 break 206 } 207 208 case *uefi.BIOSRegion: 209 if f.FlashRegion() != nil && !f.FlashRegion().Valid() { 210 v.Errors = append(v.Errors, fmt.Errorf("BIOSRegion is not valid, region was %v", *f.FlashRegion())) 211 } 212 213 if _, err := f.FirstFV(); err != nil { 214 v.Errors = append(v.Errors, err) 215 } 216 217 for i, e := range f.Elements { 218 if err := e.Value.Apply(v); err != nil { 219 return err 220 } 221 f, ok := e.Value.(*uefi.FirmwareVolume) 222 if !ok { 223 // Not a firmware volume 224 continue 225 } 226 // We have to do this because they didn't put an encapsulating structure around the FVs. 227 // This means it's possible for different firmware volumes to report different erase polarities. 228 // Now we have to check to see if we're in some insane state. 229 if ep := f.GetErasePolarity(); ep != uefi.Attributes.ErasePolarity { 230 v.Errors = append(v.Errors, fmt.Errorf("erase polarity mismatch! fv 0 has %#x and fv %d has %#x", 231 uefi.Attributes.ErasePolarity, i, ep)) 232 } 233 } 234 return nil // We already traversed the children manually. 235 236 case *uefi.MERegion: 237 if f.FlashRegion() == nil { 238 v.Errors = append(v.Errors, errors.New("region position is nil")) 239 } 240 if !f.FlashRegion().Valid() { 241 v.Errors = append(v.Errors, fmt.Errorf("region is not valid, region was %v", *f.FlashRegion())) 242 } 243 244 case *uefi.RawRegion: 245 if f.FlashRegion() == nil { 246 v.Errors = append(v.Errors, errors.New("region position is nil")) 247 } 248 if !f.FlashRegion().Valid() { 249 v.Errors = append(v.Errors, fmt.Errorf("region is not valid, region was %v", *f.FlashRegion())) 250 } 251 } 252 return f.ApplyChildren(v) 253 } 254 255 func init() { 256 RegisterCLI("validate", "perform extra validation checks", 0, func(args []string) (uefi.Visitor, error) { 257 return &Validate{}, nil 258 }) 259 }