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 }