github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/efivarfs/varfs.go (about)

     1  // Copyright 2022 the u-root 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 efivarfs
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/binary"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"os"
    14  	"path/filepath"
    15  	"sort"
    16  
    17  	guid "github.com/google/uuid"
    18  	"golang.org/x/sys/unix"
    19  )
    20  
    21  // DefaultVarFS is the path to the efivarfs mount point
    22  const DefaultVarFS = "/sys/firmware/efi/efivars/"
    23  
    24  var (
    25  	// ErrVarsUnavailable is caused by not having a valid backend
    26  	ErrVarsUnavailable = fmt.Errorf("no variable backend is available:%w", os.ErrNotExist)
    27  
    28  	// ErrVarNotExist is caused by accessing a non-existing variable
    29  	ErrVarNotExist = os.ErrNotExist
    30  
    31  	// ErrVarPermission is caused by not haven the right permissions either
    32  	// because of not being root or xattrs not allowing changes
    33  	ErrVarPermission = os.ErrPermission
    34  
    35  	// ErrNoFS is returned when the file system is not available for some
    36  	// reason.
    37  	ErrNoFS = errors.New("varfs not available")
    38  )
    39  
    40  // EFIVar is the interface for EFI variables. Note that it need not use a file system,
    41  // but typically does.
    42  type EFIVar interface {
    43  	Get(desc VariableDescriptor) (VariableAttributes, []byte, error)
    44  	List() ([]VariableDescriptor, error)
    45  	Remove(desc VariableDescriptor) error
    46  	Set(desc VariableDescriptor, attrs VariableAttributes, data []byte) error
    47  }
    48  
    49  // EFIVarFS implements EFIVar
    50  type EFIVarFS struct {
    51  	path string
    52  }
    53  
    54  var _ EFIVar = &EFIVarFS{}
    55  
    56  // NewPath returns an EFIVarFS given a path.
    57  func NewPath(p string) (*EFIVarFS, error) {
    58  	e := &EFIVarFS{path: p}
    59  	if err := e.probe(); err != nil {
    60  		return nil, err
    61  	}
    62  	return e, nil
    63  }
    64  
    65  // New returns an EFIVarFS using the default path.
    66  func New() (*EFIVarFS, error) {
    67  	return NewPath(DefaultVarFS)
    68  }
    69  
    70  // probe will probe for the EFIVarFS filesystem
    71  // magic value on the expected mountpoint inside the sysfs.
    72  // If the correct magic value was found it will return
    73  // the a pointer to an EFIVarFS struct on which regular
    74  // operations can be done. Otherwise it will return an
    75  // error of type ErrFsNotMounted.
    76  func (v *EFIVarFS) probe() error {
    77  	var stat unix.Statfs_t
    78  	if err := unix.Statfs(v.path, &stat); err != nil {
    79  		return fmt.Errorf("%w: not mounted", ErrNoFS)
    80  	}
    81  	if uint(stat.Type) != uint(unix.EFIVARFS_MAGIC) {
    82  		return fmt.Errorf("%w: wrong magic", ErrNoFS)
    83  	}
    84  	return nil
    85  }
    86  
    87  // Get reads the contents of an efivar if it exists and has the necessary permission
    88  func (v *EFIVarFS) Get(desc VariableDescriptor) (VariableAttributes, []byte, error) {
    89  	path := filepath.Join(v.path, fmt.Sprintf("%s-%s", desc.Name, desc.GUID.String()))
    90  	f, err := os.OpenFile(path, os.O_RDONLY, 0)
    91  	switch {
    92  	case os.IsNotExist(err):
    93  		return 0, nil, ErrVarNotExist
    94  	case os.IsPermission(err):
    95  		return 0, nil, ErrVarPermission
    96  	case err != nil:
    97  		return 0, nil, err
    98  	}
    99  	defer f.Close()
   100  
   101  	var attrs VariableAttributes
   102  	if err := binary.Read(f, binary.LittleEndian, &attrs); err != nil {
   103  		if err == io.EOF {
   104  			return 0, nil, ErrVarNotExist
   105  		}
   106  		return 0, nil, err
   107  	}
   108  
   109  	data, err := io.ReadAll(f)
   110  	if err != nil {
   111  		return 0, nil, err
   112  	}
   113  	return attrs, data, nil
   114  }
   115  
   116  // Set modifies a given efivar with the provided contents
   117  func (v *EFIVarFS) Set(desc VariableDescriptor, attrs VariableAttributes, data []byte) error {
   118  	path := filepath.Join(v.path, fmt.Sprintf("%s-%s", desc.Name, desc.GUID.String()))
   119  	flags := os.O_WRONLY | os.O_CREATE
   120  	if attrs&AttributeAppendWrite != 0 {
   121  		flags |= os.O_APPEND
   122  	}
   123  
   124  	read, err := os.OpenFile(path, os.O_RDONLY, 0)
   125  	switch {
   126  	case os.IsNotExist(err):
   127  	case os.IsPermission(err):
   128  		return ErrVarPermission
   129  	case err != nil:
   130  		return err
   131  	default:
   132  		defer read.Close()
   133  
   134  		restoreImmutable, err := makeMutable(read)
   135  		switch {
   136  		case os.IsPermission(err):
   137  			return ErrVarPermission
   138  		case err != nil:
   139  			return err
   140  		}
   141  		defer restoreImmutable()
   142  	}
   143  
   144  	write, err := os.OpenFile(path, flags, 0644)
   145  	switch {
   146  	case os.IsNotExist(err):
   147  		return ErrVarNotExist
   148  	case os.IsPermission(err):
   149  		return ErrVarPermission
   150  	case err != nil:
   151  		return err
   152  	}
   153  	defer write.Close()
   154  
   155  	var buf bytes.Buffer
   156  	if err := binary.Write(&buf, binary.LittleEndian, attrs); err != nil {
   157  		return err
   158  	}
   159  	if _, err := buf.Write(data); err != nil {
   160  		return err
   161  	}
   162  	if _, err := buf.WriteTo(write); err != nil {
   163  		return err
   164  	}
   165  	return nil
   166  }
   167  
   168  // Remove makes the specified EFI var mutable and then deletes it
   169  func (v *EFIVarFS) Remove(desc VariableDescriptor) error {
   170  	path := filepath.Join(v.path, fmt.Sprintf("%s-%s", desc.Name, desc.GUID.String()))
   171  	f, err := os.OpenFile(path, os.O_WRONLY, 0)
   172  	switch {
   173  	case os.IsNotExist(err):
   174  		return ErrVarNotExist
   175  	case os.IsPermission(err):
   176  		return ErrVarPermission
   177  	case err != nil:
   178  		return err
   179  	default:
   180  		_, err := makeMutable(f)
   181  		switch {
   182  		case os.IsPermission(err):
   183  			return ErrVarPermission
   184  		case err != nil:
   185  			return err
   186  		default:
   187  			f.Close()
   188  		}
   189  	}
   190  	return os.Remove(path)
   191  }
   192  
   193  // List returns the VariableDescriptor for each efivar in the system
   194  // TODO: why can't list implement
   195  func (v *EFIVarFS) List() ([]VariableDescriptor, error) {
   196  	const guidLength = 36
   197  	f, err := os.OpenFile(v.path, os.O_RDONLY, 0)
   198  	switch {
   199  	case os.IsNotExist(err):
   200  		return nil, ErrVarNotExist
   201  	case os.IsPermission(err):
   202  		return nil, ErrVarPermission
   203  	case err != nil:
   204  		return nil, err
   205  	}
   206  	defer f.Close()
   207  
   208  	dirents, err := f.Readdir(-1)
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  	var entries []VariableDescriptor
   213  	for _, dirent := range dirents {
   214  		if !dirent.Mode().IsRegular() {
   215  			// Skip non-regular files
   216  			continue
   217  		}
   218  		if len(dirent.Name()) < guidLength+1 {
   219  			// Skip files with a basename that isn't long enough
   220  			// to contain a GUID and a hyphen
   221  			continue
   222  		}
   223  		if dirent.Name()[len(dirent.Name())-guidLength-1] != '-' {
   224  			// Skip files where the basename doesn't contain a
   225  			// hyphen between the name and GUID
   226  			continue
   227  		}
   228  		if dirent.Size() == 0 {
   229  			// Skip files with zero size. These are variables that
   230  			// have been deleted by writing an empty payload
   231  			continue
   232  		}
   233  
   234  		name := dirent.Name()[:len(dirent.Name())-guidLength-1]
   235  		guid, err := guid.Parse(dirent.Name()[len(name)+1:])
   236  		if err != nil {
   237  			continue
   238  		}
   239  
   240  		entries = append(entries, VariableDescriptor{Name: name, GUID: guid})
   241  	}
   242  
   243  	sort.Slice(entries, func(i, j int) bool {
   244  		return fmt.Sprintf("%s-%v", entries[i].Name, entries[i].GUID) < fmt.Sprintf("%s-%v", entries[j].Name, entries[j].GUID)
   245  	})
   246  	return entries, nil
   247  }