gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/bootloader/efi/efi.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  // Package efi supports reading EFI variables.
    21  package efi
    22  
    23  import (
    24  	"bytes"
    25  	"encoding/binary"
    26  	"errors"
    27  	"fmt"
    28  	"io"
    29  	"io/ioutil"
    30  	"os"
    31  	"path/filepath"
    32  	"unicode/utf16"
    33  
    34  	"github.com/snapcore/snapd/dirs"
    35  	"github.com/snapcore/snapd/osutil"
    36  )
    37  
    38  var ErrNoEFISystem = errors.New("not a supported EFI system")
    39  
    40  type VariableAttr uint32
    41  
    42  // see https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/linux/efi.h?h=v5.4.32
    43  const (
    44  	VariableNonVolatile       VariableAttr = 0x00000001
    45  	VariableBootServiceAccess VariableAttr = 0x00000002
    46  	VariableRuntimeAccess     VariableAttr = 0x00000004
    47  )
    48  
    49  var (
    50  	openEFIVar = openEFIVarImpl
    51  )
    52  
    53  const expectedEFIvarfsDir = "/sys/firmware/efi/efivars"
    54  
    55  func openEFIVarImpl(name string) (r io.ReadCloser, attr VariableAttr, size int64, err error) {
    56  	mounts, err := osutil.LoadMountInfo()
    57  	if err != nil {
    58  		return nil, 0, 0, err
    59  	}
    60  	found := false
    61  	for _, mnt := range mounts {
    62  		if mnt.MountDir == expectedEFIvarfsDir {
    63  			if mnt.FsType == "efivarfs" {
    64  				found = true
    65  				break
    66  			}
    67  		}
    68  	}
    69  	if !found {
    70  		return nil, 0, 0, ErrNoEFISystem
    71  	}
    72  	varf, err := os.Open(filepath.Join(dirs.GlobalRootDir, expectedEFIvarfsDir, name))
    73  	if err != nil {
    74  		return nil, 0, 0, err
    75  	}
    76  	defer func() {
    77  		if err != nil {
    78  			varf.Close()
    79  		}
    80  	}()
    81  	fi, err := varf.Stat()
    82  	if err != nil {
    83  		return nil, 0, 0, err
    84  	}
    85  	sz := fi.Size()
    86  	if sz < 4 {
    87  		return nil, 0, 0, fmt.Errorf("unexpected size: %d", sz)
    88  	}
    89  
    90  	if err = binary.Read(varf, binary.LittleEndian, &attr); err != nil {
    91  		return nil, 0, 0, err
    92  	}
    93  	return varf, attr, sz - 4, nil
    94  }
    95  
    96  func cannotReadError(name string, err error) error {
    97  	return fmt.Errorf("cannot read EFI var %q: %v", name, err)
    98  }
    99  
   100  // ReadVarBytes will attempt to read the bytes of the value of the
   101  // specified EFI variable, specified by its full name composed of the
   102  // variable name and vendor ID. It also returns the attribute value
   103  // attached to it. It expects to use the efivars filesystem at
   104  // /sys/firmware/efi/efivars.
   105  // https://www.kernel.org/doc/Documentation/filesystems/efivarfs.txt
   106  // for more details.
   107  func ReadVarBytes(name string) ([]byte, VariableAttr, error) {
   108  	varf, attr, _, err := openEFIVar(name)
   109  	if err != nil {
   110  		if err == ErrNoEFISystem {
   111  			return nil, 0, err
   112  		}
   113  		return nil, 0, cannotReadError(name, err)
   114  	}
   115  	defer varf.Close()
   116  	b, err := ioutil.ReadAll(varf)
   117  	if err != nil {
   118  		return nil, 0, cannotReadError(name, err)
   119  	}
   120  	return b, attr, nil
   121  }
   122  
   123  // ReadVarString will attempt to read the string value of the
   124  // specified EFI variable, specified by its full name composed of the
   125  // variable name and vendor ID. The string value is expected to be
   126  // encoded as UTF16. It also returns the attribute value attached to
   127  // it. It expects to use the efivars filesystem at
   128  // /sys/firmware/efi/efivars.
   129  // https://www.kernel.org/doc/Documentation/filesystems/efivarfs.txt
   130  // for more details.
   131  func ReadVarString(name string) (string, VariableAttr, error) {
   132  	varf, attr, sz, err := openEFIVar(name)
   133  	if err != nil {
   134  		if err == ErrNoEFISystem {
   135  			return "", 0, err
   136  		}
   137  		return "", 0, cannotReadError(name, err)
   138  	}
   139  	defer varf.Close()
   140  	// TODO: consider using golang.org/x/text/encoding/unicode here
   141  	if sz%2 != 0 {
   142  		return "", 0, fmt.Errorf("EFI var %q is not a valid UTF16 string, it has an extra byte", name)
   143  	}
   144  	n := int(sz / 2)
   145  	if n == 0 {
   146  		return "", attr, nil
   147  	}
   148  	r16 := make([]uint16, n)
   149  	if err := binary.Read(varf, binary.LittleEndian, r16); err != nil {
   150  		return "", 0, cannotReadError(name, err)
   151  	}
   152  	if r16[n-1] == 0 {
   153  		n--
   154  	}
   155  	b := &bytes.Buffer{}
   156  	for _, r := range utf16.Decode(r16[:n]) {
   157  		b.WriteRune(r)
   158  	}
   159  	return b.String(), attr, nil
   160  }
   161  
   162  // MockVars mocks EFI variables as read by ReadVar*, only to be used
   163  // from tests. Set vars to nil to mock a non-EFI system.
   164  func MockVars(vars map[string][]byte, attrs map[string]VariableAttr) (restore func()) {
   165  	osutil.MustBeTestBinary("MockVars only to be used from tests")
   166  	old := openEFIVar
   167  	openEFIVar = func(name string) (io.ReadCloser, VariableAttr, int64, error) {
   168  		if vars == nil {
   169  			return nil, 0, 0, ErrNoEFISystem
   170  		}
   171  		if val, ok := vars[name]; ok {
   172  			attr, ok := attrs[name]
   173  			if !ok {
   174  				attr = VariableRuntimeAccess | VariableBootServiceAccess
   175  			}
   176  			return ioutil.NopCloser(bytes.NewBuffer(val)), attr, int64(len(val)), nil
   177  		}
   178  		return nil, 0, 0, fmt.Errorf("EFI variable %s not mocked", name)
   179  	}
   180  
   181  	return func() {
   182  		openEFIVar = old
   183  	}
   184  }