blob: f6c489b70a3d94697ebb0fa926df9ebd685de8c3 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Mateusz Zalegac6c092b2021-11-09 13:09:37 +01002// SPDX-License-Identifier: Apache-2.0
Mateusz Zalegac6c092b2021-11-09 13:09:37 +01003
Lorenz Brunca1cff02023-06-26 17:52:44 +02004// Package efivarfs provides functions to read and manipulate UEFI runtime
5// variables. It uses Linux's efivarfs [1] to access the variables and all
6// functions generally require that this is mounted at
7// "/sys/firmware/efi/efivars".
Mateusz Zalegac6c092b2021-11-09 13:09:37 +01008//
Lorenz Brunca1cff02023-06-26 17:52:44 +02009// [1] https://www.kernel.org/doc/html/latest/filesystems/efivarfs.html
Mateusz Zalegac6c092b2021-11-09 13:09:37 +010010package efivarfs
11
12import (
Lorenz Brunca1cff02023-06-26 17:52:44 +020013 "encoding/binary"
14 "errors"
Mateusz Zalegac6c092b2021-11-09 13:09:37 +010015 "fmt"
Lorenz Brunca1cff02023-06-26 17:52:44 +020016 "io/fs"
Mateusz Zalega5b60e582021-11-10 19:57:17 +010017 "os"
Lorenz Brunca1cff02023-06-26 17:52:44 +020018 "strings"
Mateusz Zalegac6c092b2021-11-09 13:09:37 +010019
Lorenz Brunca9cfcf2023-05-02 19:29:14 +020020 "github.com/google/uuid"
Mateusz Zalegac6c092b2021-11-09 13:09:37 +010021 "golang.org/x/text/encoding/unicode"
22)
23
24const (
Lorenz Brunca1cff02023-06-26 17:52:44 +020025 Path = "/sys/firmware/efi/efivars"
26)
27
28var (
29 // ScopeGlobal is the scope of variables defined by the EFI specification
30 // itself.
31 ScopeGlobal = uuid.MustParse("8be4df61-93ca-11d2-aa0d-00e098032b8c")
32 // ScopeSystemd is the scope of variables defined by Systemd/bootspec.
33 ScopeSystemd = uuid.MustParse("4a67b082-0a4c-41cf-b6c7-440b29bb8c4f")
Mateusz Zalegac6c092b2021-11-09 13:09:37 +010034)
35
Mateusz Zalega6cefe512021-11-08 18:19:42 +010036// Encoding defines the Unicode encoding used by UEFI, which is UCS-2 Little
37// Endian. For BMP characters UTF-16 is equivalent to UCS-2. See the UEFI
38// Spec 2.9, Sections 33.2.6 and 1.8.1.
39var Encoding = unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
40
Lorenz Brunca1cff02023-06-26 17:52:44 +020041// Attribute contains a bitset of EFI variable attributes.
42type Attribute uint32
43
44const (
45 // If set the value of the variable is is persistent across resets and
46 // power cycles. Variables without this set cannot be created or modified
47 // after UEFI boot services are terminated.
48 AttrNonVolatile Attribute = 1 << iota
49 // If set allows access to this variable from UEFI boot services.
50 AttrBootserviceAccess
51 // If set allows access to this variable from an operating system after
52 // UEFI boot services are terminated. Variables setting this must also
53 // set AttrBootserviceAccess. This is automatically taken care of by Write
54 // in this package.
55 AttrRuntimeAccess
56 // Marks a variable as being a hardware error record. See UEFI 2.10 section
57 // 8.2.8 for more information about this.
58 AttrHardwareErrorRecord
59 // Deprecated, should not be used for new variables.
60 AttrAuthenticatedWriteAccess
61 // Variable requires special authentication to write. These variables
62 // cannot be written with this package.
63 AttrTimeBasedAuthenticatedWriteAccess
64 // If set in a Write() call, tries to append the data instead of replacing
65 // it completely.
66 AttrAppendWrite
67 // Variable requires special authentication to access and write. These
68 // variables cannot be accessed with this package.
69 AttrEnhancedAuthenticatedAccess
70)
71
72func varPath(scope uuid.UUID, varName string) string {
73 return fmt.Sprintf("/sys/firmware/efi/efivars/%s-%s", varName, scope.String())
Mateusz Zalegac6c092b2021-11-09 13:09:37 +010074}
75
Lorenz Brunca1cff02023-06-26 17:52:44 +020076// Write writes the value of the named variable in the given scope.
77func Write(scope uuid.UUID, varName string, attrs Attribute, value []byte) error {
78 // Write attributes, see @linux//Documentation/filesystems:efivarfs.rst for format
Tim Windelschmidtd07489b2023-08-03 13:09:02 +000079 f, err := os.OpenFile(varPath(scope, varName), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
Mateusz Zalegac6c092b2021-11-09 13:09:37 +010080 if err != nil {
Lorenz Brunca1cff02023-06-26 17:52:44 +020081 e := err
82 // Unwrap PathError here as we wrap our own parameter message around it
83 var perr *fs.PathError
84 if errors.As(err, &perr) {
85 e = perr.Err
Mateusz Zalega5b60e582021-11-10 19:57:17 +010086 }
Lorenz Brunca1cff02023-06-26 17:52:44 +020087 return fmt.Errorf("writing %q in scope %s: %w", varName, scope, e)
Mateusz Zalega5b60e582021-11-10 19:57:17 +010088 }
Lorenz Brunca1cff02023-06-26 17:52:44 +020089 // Required by UEFI 2.10 Section 8.2.3:
90 // Runtime access to a data variable implies boot service access. Attributes
91 // that have EFI_VARIABLE_RUNTIME_ACCESS set must also have
92 // EFI_VARIABLE_BOOTSERVICE_ACCESS set. The caller is responsible for
93 // following this rule.
94 if attrs&AttrRuntimeAccess != 0 {
95 attrs |= AttrBootserviceAccess
Mateusz Zalega5b60e582021-11-10 19:57:17 +010096 }
Lorenz Brunca1cff02023-06-26 17:52:44 +020097 // Linux wants everything in on write, so assemble an intermediate buffer
98 buf := make([]byte, len(value)+4)
99 binary.LittleEndian.PutUint32(buf[:4], uint32(attrs))
100 copy(buf[4:], value)
101 _, err = f.Write(buf)
102 if err1 := f.Close(); err1 != nil && err == nil {
103 err = err1
Mateusz Zalega5b60e582021-11-10 19:57:17 +0100104 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200105 return err
Mateusz Zalega5b60e582021-11-10 19:57:17 +0100106}
107
Lorenz Brunca1cff02023-06-26 17:52:44 +0200108// Read reads the value of the named variable in the given scope.
109func Read(scope uuid.UUID, varName string) ([]byte, Attribute, error) {
110 val, err := os.ReadFile(varPath(scope, varName))
111 if err != nil {
112 e := err
113 // Unwrap PathError here as we wrap our own parameter message around it
114 var perr *fs.PathError
115 if errors.As(err, &perr) {
116 e = perr.Err
117 }
118 return nil, Attribute(0), fmt.Errorf("reading %q in scope %s: %w", varName, scope, e)
Mateusz Zalega5b60e582021-11-10 19:57:17 +0100119 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200120 if len(val) < 4 {
121 return nil, Attribute(0), fmt.Errorf("reading %q in scope %s: malformed, less than 4 bytes long", varName, scope)
122 }
123 return val[4:], Attribute(binary.LittleEndian.Uint32(val[:4])), nil
124}
125
126// List lists all variable names present for a given scope sorted by their names
127// in Go's "native" string sort order.
128func List(scope uuid.UUID) ([]string, error) {
129 vars, err := os.ReadDir(Path)
130 if err != nil {
131 return nil, fmt.Errorf("failed to list variable directory: %w", err)
132 }
133 var outVarNames []string
134 suffix := fmt.Sprintf("-%v", scope)
135 for _, v := range vars {
136 if v.IsDir() {
137 continue
138 }
139 if !strings.HasSuffix(v.Name(), suffix) {
140 continue
141 }
142 outVarNames = append(outVarNames, strings.TrimSuffix(v.Name(), suffix))
143 }
144 return outVarNames, nil
Mateusz Zalega5b60e582021-11-10 19:57:17 +0100145}
Lorenz Bruna7cfc1c2023-11-30 18:59:25 +0100146
147// Delete deletes the given variable name in the given scope. Use with care,
148// some firmware fails to boot if variables it uses are deleted.
149func Delete(scope uuid.UUID, varName string) error {
150 return os.Remove(varPath(scope, varName))
151}