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, &timestamp); 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  }