| Tim Windelschmidt | 6d33a43 | 2025-02-04 14:34:25 +0100 | [diff] [blame] | 1 | // Copyright The Monogon Project Authors. |
| Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 2 | // SPDX-License-Identifier: Apache-2.0 |
| Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 3 | |
| Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame] | 4 | // 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 Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 8 | // |
| Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame] | 9 | // [1] https://www.kernel.org/doc/html/latest/filesystems/efivarfs.html |
| Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 10 | package efivarfs |
| 11 | |
| 12 | import ( |
| Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame] | 13 | "encoding/binary" |
| 14 | "errors" |
| Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 15 | "fmt" |
| Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame] | 16 | "io/fs" |
| Mateusz Zalega | 5b60e58 | 2021-11-10 19:57:17 +0100 | [diff] [blame] | 17 | "os" |
| Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame] | 18 | "strings" |
| Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 19 | |
| Lorenz Brun | ca9cfcf | 2023-05-02 19:29:14 +0200 | [diff] [blame] | 20 | "github.com/google/uuid" |
| Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 21 | "golang.org/x/text/encoding/unicode" |
| 22 | ) |
| 23 | |
| 24 | const ( |
| Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame] | 25 | Path = "/sys/firmware/efi/efivars" |
| 26 | ) |
| 27 | |
| 28 | var ( |
| 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 Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 34 | ) |
| 35 | |
| Mateusz Zalega | 6cefe51 | 2021-11-08 18:19:42 +0100 | [diff] [blame] | 36 | // 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. |
| 39 | var Encoding = unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM) |
| 40 | |
| Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame] | 41 | // Attribute contains a bitset of EFI variable attributes. |
| 42 | type Attribute uint32 |
| 43 | |
| 44 | const ( |
| 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 | |
| 72 | func varPath(scope uuid.UUID, varName string) string { |
| 73 | return fmt.Sprintf("/sys/firmware/efi/efivars/%s-%s", varName, scope.String()) |
| Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 74 | } |
| 75 | |
| Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame] | 76 | // Write writes the value of the named variable in the given scope. |
| 77 | func Write(scope uuid.UUID, varName string, attrs Attribute, value []byte) error { |
| 78 | // Write attributes, see @linux//Documentation/filesystems:efivarfs.rst for format |
| Tim Windelschmidt | d07489b | 2023-08-03 13:09:02 +0000 | [diff] [blame] | 79 | f, err := os.OpenFile(varPath(scope, varName), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) |
| Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 80 | if err != nil { |
| Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame] | 81 | 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 Zalega | 5b60e58 | 2021-11-10 19:57:17 +0100 | [diff] [blame] | 86 | } |
| Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame] | 87 | return fmt.Errorf("writing %q in scope %s: %w", varName, scope, e) |
| Mateusz Zalega | 5b60e58 | 2021-11-10 19:57:17 +0100 | [diff] [blame] | 88 | } |
| Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame] | 89 | // 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 Zalega | 5b60e58 | 2021-11-10 19:57:17 +0100 | [diff] [blame] | 96 | } |
| Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame] | 97 | // 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 Zalega | 5b60e58 | 2021-11-10 19:57:17 +0100 | [diff] [blame] | 104 | } |
| Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame] | 105 | return err |
| Mateusz Zalega | 5b60e58 | 2021-11-10 19:57:17 +0100 | [diff] [blame] | 106 | } |
| 107 | |
| Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame] | 108 | // Read reads the value of the named variable in the given scope. |
| 109 | func 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 Zalega | 5b60e58 | 2021-11-10 19:57:17 +0100 | [diff] [blame] | 119 | } |
| Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame] | 120 | 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. |
| 128 | func 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 Zalega | 5b60e58 | 2021-11-10 19:57:17 +0100 | [diff] [blame] | 145 | } |
| Lorenz Brun | a7cfc1c | 2023-11-30 18:59:25 +0100 | [diff] [blame] | 146 | |
| 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. |
| 149 | func Delete(scope uuid.UUID, varName string) error { |
| 150 | return os.Remove(varPath(scope, varName)) |
| 151 | } |