github.com/linuxboot/fiano@v1.2.0/pkg/uefi/nvram.go (about) 1 // Copyright 2019 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 // NVAR decoding logic ported from UEFITool Copyright (c) 2016, Nikolaj Schlej. 6 // https://github.com/LongSoft/UEFITool/blob/new_engine/common/nvramparser.cpp 7 // The author described his reverse engineering work on his blog: 8 // https://habr.com/en/post/281901/ 9 10 package uefi 11 12 import ( 13 "bytes" 14 "crypto/sha256" 15 "encoding/binary" 16 "errors" 17 "fmt" 18 "io" 19 20 "github.com/linuxboot/fiano/pkg/guid" 21 "github.com/linuxboot/fiano/pkg/unicode" 22 ) 23 24 // NVarAttribute represent Attributes 25 type NVarAttribute uint8 26 27 // Attributes 28 const ( 29 NVarEntryRuntime NVarAttribute = 0x01 30 NVarEntryASCIIName NVarAttribute = 0x02 31 NVarEntryGUID NVarAttribute = 0x04 32 NVarEntryDataOnly NVarAttribute = 0x08 33 NVarEntryExtHeader NVarAttribute = 0x10 34 NVarEntryHWErrorRecord NVarAttribute = 0x20 35 NVarEntryAuthWrite NVarAttribute = 0x40 36 NVarEntryValid NVarAttribute = 0x80 37 ) 38 39 // IsValid returns the Valid attribute as boolean 40 func (a NVarAttribute) IsValid() bool { 41 return a&NVarEntryValid != 0 42 } 43 44 // NVarEntrySignature value for 'NVAR' signature 45 const NVarEntrySignature uint32 = 0x5241564E 46 47 // NVarHeader represents an NVAR entry header 48 type NVarHeader struct { 49 Signature uint32 `json:"-"` 50 Size uint16 51 Next [3]uint8 `json:"-"` 52 Attributes NVarAttribute 53 } 54 55 // NVarEntryType represent the computed type of an NVAR entry 56 type NVarEntryType uint8 57 58 // Types 59 const ( 60 InvalidNVarEntry NVarEntryType = iota 61 InvalidLinkNVarEntry 62 LinkNVarEntry 63 DataNVarEntry 64 FullNVarEntry 65 ) 66 67 var nVarEntryTypeName = map[NVarEntryType]string{ 68 InvalidNVarEntry: "Invalid", 69 InvalidLinkNVarEntry: "Invalid link", 70 LinkNVarEntry: "Link", 71 DataNVarEntry: "Data", 72 FullNVarEntry: "Full", 73 } 74 75 func (t NVarEntryType) String() string { 76 if s, ok := nVarEntryTypeName[t]; ok { 77 return s 78 } 79 return "UNKNOWN" 80 } 81 82 // NVarExtAttribute represent extended attributes 83 type NVarExtAttribute uint8 84 85 // Extended attributes values 86 const ( 87 NVarEntryExtChecksum NVarExtAttribute = 0x01 88 NVarEntryExtAuthWrite NVarExtAttribute = 0x10 89 NVarEntryExtTimeBased NVarExtAttribute = 0x20 90 NVarEntryExtUnknownMask NVarExtAttribute = 0xCE 91 ) 92 93 // NVar represent an NVAR entry 94 type NVar struct { 95 Header NVarHeader 96 GUID guid.GUID 97 GUIDIndex *uint8 `json:",omitempty"` 98 Name string 99 100 NVarStore *NVarStore `json:",omitempty"` 101 102 //Decoded data 103 Type NVarEntryType 104 Offset uint64 105 NextOffset uint64 106 107 //Extended Header 108 ExtAttributes *NVarExtAttribute `json:",omitempty"` 109 Checksum *uint8 `json:",omitempty"` 110 ExpectedChecksum *uint8 `json:",omitempty"` 111 TimeStamp *uint64 `json:",omitempty"` 112 Hash []byte `json:",omitempty"` 113 UnknownExtendedHeaderFormat bool `json:",omitempty"` 114 115 //Metadata for extraction and recovery 116 buf []byte 117 ExtractPath string 118 DataOffset int64 119 ExtOffset int64 `json:",omitempty"` 120 } 121 122 // String returns the String value of the NVAR: Type and Name if valid 123 func (v *NVar) String() string { 124 if v.IsValid() { 125 return fmt.Sprintf("[%s] %v", v.Type, v.Name) 126 } 127 return fmt.Sprintf("[%s]", v.Type) 128 } 129 130 // Buf returns the buffer. 131 // Used mostly for things interacting with the Firmware interface. 132 func (v *NVar) Buf() []byte { 133 return v.buf 134 } 135 136 // SetBuf sets the buffer. 137 // Used mostly for things interacting with the Firmware interface. 138 func (v *NVar) SetBuf(buf []byte) { 139 v.buf = buf 140 } 141 142 // Apply calls the visitor on the NVar. 143 func (v *NVar) Apply(vr Visitor) error { 144 return vr.Visit(v) 145 } 146 147 // ApplyChildren calls the visitor on each child node of NVar. 148 func (v *NVar) ApplyChildren(vr Visitor) error { 149 if v.NVarStore != nil { 150 if err := v.NVarStore.Apply(vr); err != nil { 151 return err 152 } 153 } 154 return nil 155 } 156 157 // NVarStore represent an NVAR store 158 type NVarStore struct { 159 Entries []*NVar 160 GUIDStore []guid.GUID `json:",omitempty"` 161 162 //Metadata for extraction and recovery 163 buf []byte 164 FreeSpaceOffset uint64 165 GUIDStoreOffset uint64 166 Length uint64 167 } 168 169 // Buf returns the buffer. 170 // Used mostly for things interacting with the Firmware interface. 171 func (s *NVarStore) Buf() []byte { 172 return s.buf 173 } 174 175 // SetBuf sets the buffer. 176 // Used mostly for things interacting with the Firmware interface. 177 func (s *NVarStore) SetBuf(buf []byte) { 178 s.buf = buf 179 } 180 181 // Apply calls the visitor on the NVarStore. 182 func (s *NVarStore) Apply(v Visitor) error { 183 return v.Visit(s) 184 } 185 186 // ApplyChildren calls the visitor on each child node of NVarStore. 187 func (s *NVarStore) ApplyChildren(v Visitor) error { 188 for _, nv := range s.Entries { 189 if err := nv.Apply(v); err != nil { 190 return err 191 } 192 } 193 return nil 194 } 195 196 func (s *NVarStore) getGUIDFromStore(i uint8) guid.GUID { 197 var GUID guid.GUID 198 if len(s.GUIDStore) < int(i+1) { 199 // Read GUID in reverse order from the buffer 200 r := bytes.NewReader(s.buf) 201 if _, err := r.Seek(-int64(binary.Size(GUID))*int64(i+1), io.SeekEnd); err != nil { 202 // not returning an error as this is really unlikely, in most 203 // overflow case we will read NVAR content as GUID as the store 204 // buffer is expected to be big enough... 205 return *ZeroGUID 206 } 207 a := make([]guid.GUID, int(i+1)-len(s.GUIDStore)) 208 for j := int(i) - len(s.GUIDStore); j >= 0; j-- { 209 // no error check as the Seek will fail first 210 if err := binary.Read(r, binary.LittleEndian, &a[j]); err != nil { 211 break 212 } 213 } 214 s.GUIDStore = append(s.GUIDStore, a...) 215 } 216 return s.GUIDStore[i] 217 } 218 219 func (v *NVar) parseHeader(buf []byte) error { 220 // Read in standard header. 221 r := bytes.NewReader(buf) 222 if err := binary.Read(r, binary.LittleEndian, &v.Header); err != nil { 223 return err 224 } 225 if v.Header.Signature != NVarEntrySignature { 226 return fmt.Errorf("NVAR Signature not found") 227 } 228 if len(buf) < int(v.Header.Size) { 229 return fmt.Errorf("NVAR Size bigger than remaining size") 230 } 231 v.DataOffset = int64(binary.Size(v.Header)) 232 return nil 233 } 234 235 // IsValid tells whether an entry is valid 236 func (v *NVar) IsValid() bool { 237 switch v.Type { 238 case LinkNVarEntry, DataNVarEntry, FullNVarEntry: 239 return true 240 default: 241 return false 242 } 243 } 244 245 func (v *NVar) parseNext() error { 246 var lastVariableFlag uint64 247 if Attributes.ErasePolarity == 0xFF { 248 lastVariableFlag = 0xFFFFFF 249 } else if Attributes.ErasePolarity == 0 { 250 lastVariableFlag = 0 251 } else { 252 return fmt.Errorf("erase polarity not 0x00 or 0xFF, got %#x", Attributes.ErasePolarity) 253 } 254 255 // Add next node information 256 next := Read3Size(v.Header.Next) 257 if next != lastVariableFlag { 258 v.Type = LinkNVarEntry 259 v.NextOffset = v.Offset + next 260 } 261 return nil 262 } 263 264 func (v *NVar) parseExtendedHeader() error { 265 var knownExtDataFormat bool 266 if v.Header.Attributes&NVarEntryExtHeader == 0 { 267 return nil 268 } 269 var extendedHeaderSize uint16 270 r := bytes.NewReader(v.buf) 271 if _, err := r.Seek(-int64(binary.Size(extendedHeaderSize)), io.SeekEnd); err != nil { 272 return err 273 } 274 if err := binary.Read(r, binary.LittleEndian, &extendedHeaderSize); err != nil { 275 return err 276 } 277 // Sanity check extendedHeaderSize < body size 278 bodySize := int64(v.Header.Size) - v.DataOffset 279 if int64(extendedHeaderSize) > bodySize { 280 return fmt.Errorf("extended header size (%#x) is greater than body size (%#x)", extendedHeaderSize, bodySize) 281 } 282 v.ExtOffset = int64(v.Header.Size) - int64(extendedHeaderSize) 283 if _, err := r.Seek(v.ExtOffset, io.SeekStart); err != nil { 284 return err 285 } 286 var extAttributes NVarExtAttribute 287 if err := binary.Read(r, binary.LittleEndian, &extAttributes); err != nil { 288 return err 289 } 290 v.ExtAttributes = &extAttributes 291 // Variable with checksum 292 if extAttributes&NVarEntryExtChecksum != 0 { 293 // Get stored checksum 294 var storedChecksum uint8 295 storedChecksum = v.buf[int64(v.Header.Size)-int64(binary.Size(extendedHeaderSize)+binary.Size(storedChecksum))] 296 v.Checksum = &storedChecksum 297 // Recalculate checksum for the variable 298 calculatedChecksum := uint8(0) 299 // [0-3] _Skip_ entry 'NVAR' signature 300 // [4-5] Include entry size and flags 301 // [6-8] _Skip_ entry next (So linking will not invalidate the sum) 302 // [ 9 ] Include entry attributes 303 // [10-] Include entry data 304 for i := int64(4); i < int64(v.Header.Size); i++ { 305 calculatedChecksum += v.buf[i] 306 if i == 5 { 307 i += 3 // Skip Next 308 } 309 } 310 if calculatedChecksum != 0 { 311 calculatedChecksum = -calculatedChecksum 312 v.ExpectedChecksum = &calculatedChecksum 313 } 314 knownExtDataFormat = true 315 } 316 317 if v.Header.Attributes&NVarEntryAuthWrite == 0 { 318 var timestamp uint64 319 if err := binary.Read(r, binary.LittleEndian, ×tamp); err != nil { 320 switch err { 321 case io.EOF, io.ErrUnexpectedEOF: 322 return fmt.Errorf("extended header size (%#x) is too small for timestamp", extendedHeaderSize) 323 default: 324 return err 325 } 326 } 327 if v.Header.Attributes&NVarEntryDataOnly != 0 { 328 // Full or link variable have hash 329 hashstart, err := r.Seek(0, io.SeekCurrent) 330 if err != nil { 331 return err 332 } 333 if hashstart+sha256.Size > int64(v.Header.Size) { 334 return fmt.Errorf("extended header size (%#x) is too small for hash", extendedHeaderSize) 335 } 336 v.Hash = make([]byte, sha256.Size) 337 copy(v.Hash, v.buf[hashstart:hashstart+sha256.Size]) 338 } 339 knownExtDataFormat = true 340 } 341 v.UnknownExtendedHeaderFormat = !knownExtDataFormat 342 return nil 343 } 344 345 func (v *NVar) parseDataOnly(s *NVarStore) bool { 346 if v.Header.Attributes&NVarEntryDataOnly == 0 { 347 return false 348 } 349 // Search previously added entries for a link to this variable 350 // Note: We expect links to be from previous to new entries as links 351 // are used to replace the values while keeping the Name and GUID. 352 // TODO: fix if we ever met legitimate rom that defeat this assumption. 353 var link *NVar 354 for _, l := range s.Entries { 355 if l.IsValid() && l.NextOffset == v.Offset { 356 link = l 357 break 358 } 359 } 360 if link != nil { 361 v.GUID = link.GUID 362 v.Name = link.Name 363 if v.NextOffset == 0 { 364 v.Type = DataNVarEntry 365 } 366 } else { 367 v.Name = "Invalid link" 368 v.Type = InvalidLinkNVarEntry 369 } 370 371 return true 372 } 373 374 func (v *NVar) parseGUID(s *NVarStore) error { 375 r := bytes.NewReader(v.buf[v.DataOffset:]) 376 if v.Header.Attributes&NVarEntryGUID != 0 { 377 // GUID in variable 378 if err := binary.Read(r, binary.LittleEndian, &v.GUID); err != nil { 379 return err 380 } 381 v.DataOffset += int64(binary.Size(v.GUID)) 382 } else { 383 // GUID index in store 384 var guidIndex uint8 385 if err := binary.Read(r, binary.LittleEndian, &guidIndex); err != nil { 386 return err 387 } 388 v.GUIDIndex = &guidIndex 389 v.GUID = s.getGUIDFromStore(guidIndex) 390 v.DataOffset += int64(binary.Size(guidIndex)) 391 } 392 return nil 393 } 394 395 func (v *NVar) parseName() error { 396 if v.Header.Attributes&NVarEntryASCIIName != 0 { 397 // Name is stored as ASCII string of CHAR8s 398 namebuf := v.buf[v.DataOffset:] 399 end := bytes.IndexByte(namebuf, 0) 400 if end == -1 { 401 return io.EOF 402 } 403 v.Name = string(namebuf[:end]) 404 v.DataOffset += int64(end) + 1 405 } else { 406 // Name is stored as UCS2 string of CHAR16s 407 namebuf := v.buf[v.DataOffset:] 408 end := bytes.Index(namebuf, []byte{0, 0}) 409 if end == -1 { 410 return io.EOF 411 } 412 v.Name = unicode.UCS2ToUTF8(namebuf[:end]) 413 v.DataOffset += int64(end) + 2 414 } 415 return nil 416 } 417 418 func (v *NVar) parseContent(buf []byte) error { 419 // Try parsing as NVAR storage if it begins with NVAR signature 420 r := bytes.NewReader(buf) 421 var signature uint32 422 if err := binary.Read(r, binary.LittleEndian, &signature); err != nil { 423 return err 424 } 425 if signature != NVarEntrySignature { 426 return fmt.Errorf("NVAR Signature not found") 427 } 428 ns, err := NewNVarStore(buf) 429 if err != nil { 430 return fmt.Errorf("error parsing NVAR store in var %v: %v", v.Name, err) 431 } 432 v.NVarStore = ns 433 return nil 434 } 435 436 // newNVar parses a sequence of bytes and returns an NVar 437 // object, if a valid one is passed, returns nil if buf is clear, or an error. 438 func newNVar(buf []byte, offset uint64, s *NVarStore) (*NVar, error) { 439 // Check if remaining space is erased 440 if IsErased(buf, Attributes.ErasePolarity) { 441 return nil, nil 442 } 443 444 v := NVar{Type: FullNVarEntry, Offset: offset} 445 // read the header and check for existing NVAR 446 if err := v.parseHeader(buf); err != nil { 447 return nil, err 448 } 449 450 // Copy out the buffer. 451 newBuf := buf[:v.Header.Size] 452 v.buf = make([]byte, v.Header.Size) 453 copy(v.buf, newBuf) 454 455 // Entry is marked as invalid 456 if !v.Header.Attributes.IsValid() { 457 v.Name = "Invalid" 458 v.Type = InvalidNVarEntry 459 return &v, nil 460 } 461 462 // Parse next node information 463 if err := v.parseNext(); err != nil { 464 return nil, err 465 } 466 467 // Entry with extended header 468 if err := v.parseExtendedHeader(); err != nil { 469 v.Name = fmt.Sprintf("Invalid ExtHeader, %v", err) 470 v.Type = InvalidNVarEntry 471 return &v, nil 472 } 473 474 // Entry is data-only (nameless and GUIDless entry or link) 475 if !v.parseDataOnly(s) { 476 // Get entry name and GUID 477 if err := v.parseGUID(s); err != nil { 478 return nil, err 479 } 480 if err := v.parseName(); err != nil { 481 return nil, err 482 } 483 } 484 485 // Try parsing the entry content 486 _ = v.parseContent(v.buf[v.DataOffset:]) 487 488 return &v, nil 489 } 490 491 // Assemble takes in the content and assembles the NVAR binary 492 // Warning: when checkOnly is false the resulting NVar must be Assembled again 493 // to fix the header content 494 func (v *NVar) Assemble(content []byte, checkOnly bool) error { 495 if !v.IsValid() { 496 return errors.New("unable to construct Invalid NVAR") 497 } 498 vData := new(bytes.Buffer) 499 v.Header.Signature = NVarEntrySignature 500 if v.NextOffset != 0 && !checkOnly { 501 return errors.New("unable to update data in link, use compact first") 502 } 503 if v.NextOffset != 0 { 504 v.Header.Next = Write3Size(v.NextOffset - v.Offset) 505 } else { 506 v.Header.Next = [3]uint8{Attributes.ErasePolarity, Attributes.ErasePolarity, Attributes.ErasePolarity} 507 } 508 err := binary.Write(vData, binary.LittleEndian, v.Header) 509 if err != nil { 510 return fmt.Errorf("unable to construct binary header of NVAR: got %v", err) 511 } 512 // GUID 513 if v.Header.Attributes&NVarEntryDataOnly == 0 { 514 if v.Header.Attributes&NVarEntryGUID != 0 { 515 err := binary.Write(vData, binary.LittleEndian, v.GUID) 516 if err != nil { 517 return fmt.Errorf("unable to add GUID to NVAR: got %v", err) 518 } 519 } else { 520 err := binary.Write(vData, binary.LittleEndian, *v.GUIDIndex) 521 if err != nil { 522 return fmt.Errorf("unable to add GUID index to NVAR: got %v", err) 523 } 524 } 525 // Name 526 if v.Header.Attributes&NVarEntryASCIIName != 0 { 527 _, err := vData.Write([]byte(v.Name)) 528 if err != nil { 529 return fmt.Errorf("unable to add Name to NVAR: got %v", err) 530 } 531 err = vData.WriteByte(0) 532 if err != nil { 533 return fmt.Errorf("unable to add Name to NVAR: got %v", err) 534 } 535 } else { 536 _, err := vData.Write(unicode.UTF8ToUCS2(v.Name)) 537 if err != nil { 538 return fmt.Errorf("unable to add Name to NVAR: got %v", err) 539 } 540 } 541 } 542 //check/update DataOffset 543 if checkOnly { 544 if v.DataOffset != int64(vData.Len()) { 545 return fmt.Errorf("NVAR header size mismatch, expected %v got %v", v.DataOffset, vData.Len()) 546 } 547 } else { 548 v.DataOffset = int64(vData.Len()) 549 } 550 _, err = vData.Write(content) 551 if err != nil { 552 return fmt.Errorf("unable to add content to NVAR: got %v", err) 553 } 554 //check/update Header.Size 555 if checkOnly { 556 if v.Header.Size != uint16(vData.Len()) { 557 return fmt.Errorf("NVAR size mismatch, expected %v got %v", v.Header.Size, vData.Len()) 558 } 559 } else { 560 v.Header.Size = uint16(vData.Len()) 561 } 562 v.SetBuf(vData.Bytes()) 563 return nil 564 } 565 566 // NewNVarStore parses a sequence of bytes and returns an NVarStore 567 // object, if a valid one is passed, or an error. 568 func NewNVarStore(buf []byte) (*NVarStore, error) { 569 s := NVarStore{} 570 571 // Copy out the buffer. 572 s.buf = make([]byte, len(buf)) 573 copy(s.buf, buf) 574 575 s.Length = uint64(len(buf)) 576 s.GUIDStoreOffset = s.Length 577 578 for s.FreeSpaceOffset = uint64(0); s.FreeSpaceOffset < s.GUIDStoreOffset; { 579 v, err := newNVar(s.buf[s.FreeSpaceOffset:s.GUIDStoreOffset], s.FreeSpaceOffset, &s) 580 if err != nil { 581 return nil, fmt.Errorf("error parsing NVAR entry at offset %#x: %v", s.FreeSpaceOffset, err) 582 } 583 if v == nil { 584 break 585 } 586 s.Entries = append(s.Entries, v) 587 s.FreeSpaceOffset += uint64(v.Header.Size) 588 s.GUIDStoreOffset = s.Length - uint64(binary.Size(guid.GUID{}))*uint64(len(s.GUIDStore)) 589 } 590 591 return &s, nil 592 } 593 594 // GetGUIDStoreBuf returns the binary representation of the GUIDStore 595 func (s *NVarStore) GetGUIDStoreBuf() ([]byte, error) { 596 guidStoreWBuf := new(bytes.Buffer) 597 for i := len(s.GUIDStore) - 1; i >= 0; i-- { 598 err := binary.Write(guidStoreWBuf, binary.LittleEndian, s.GUIDStore[i]) 599 if err != nil { 600 return nil, fmt.Errorf("unable to write GUID index to store: got %v", err) 601 } 602 } 603 return guidStoreWBuf.Bytes(), nil 604 }