Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 1 | // Copyright 2020 The Monogon Project Authors. |
| 2 | // |
| 3 | // SPDX-License-Identifier: Apache-2.0 |
| 4 | // |
| 5 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | // you may not use this file except in compliance with the License. |
| 7 | // You may obtain a copy of the License at |
| 8 | // |
| 9 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | // |
| 11 | // Unless required by applicable law or agreed to in writing, software |
| 12 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | // See the License for the specific language governing permissions and |
| 15 | // limitations under the License. |
| 16 | |
Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame^] | 17 | // Package efivarfs provides functions to read and manipulate UEFI runtime |
| 18 | // variables. It uses Linux's efivarfs [1] to access the variables and all |
| 19 | // functions generally require that this is mounted at |
| 20 | // "/sys/firmware/efi/efivars". |
Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 21 | // |
Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame^] | 22 | // [1] https://www.kernel.org/doc/html/latest/filesystems/efivarfs.html |
Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 23 | package efivarfs |
| 24 | |
| 25 | import ( |
Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame^] | 26 | "encoding/binary" |
| 27 | "errors" |
Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 28 | "fmt" |
Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame^] | 29 | "io/fs" |
Mateusz Zalega | 5b60e58 | 2021-11-10 19:57:17 +0100 | [diff] [blame] | 30 | "os" |
Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame^] | 31 | "strings" |
Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 32 | |
Lorenz Brun | ca9cfcf | 2023-05-02 19:29:14 +0200 | [diff] [blame] | 33 | "github.com/google/uuid" |
Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 34 | "golang.org/x/text/encoding/unicode" |
| 35 | ) |
| 36 | |
| 37 | const ( |
Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame^] | 38 | Path = "/sys/firmware/efi/efivars" |
| 39 | ) |
| 40 | |
| 41 | var ( |
| 42 | // ScopeGlobal is the scope of variables defined by the EFI specification |
| 43 | // itself. |
| 44 | ScopeGlobal = uuid.MustParse("8be4df61-93ca-11d2-aa0d-00e098032b8c") |
| 45 | // ScopeSystemd is the scope of variables defined by Systemd/bootspec. |
| 46 | ScopeSystemd = uuid.MustParse("4a67b082-0a4c-41cf-b6c7-440b29bb8c4f") |
Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 47 | ) |
| 48 | |
Mateusz Zalega | 6cefe51 | 2021-11-08 18:19:42 +0100 | [diff] [blame] | 49 | // Encoding defines the Unicode encoding used by UEFI, which is UCS-2 Little |
| 50 | // Endian. For BMP characters UTF-16 is equivalent to UCS-2. See the UEFI |
| 51 | // Spec 2.9, Sections 33.2.6 and 1.8.1. |
| 52 | var Encoding = unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM) |
| 53 | |
Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame^] | 54 | // Attribute contains a bitset of EFI variable attributes. |
| 55 | type Attribute uint32 |
| 56 | |
| 57 | const ( |
| 58 | // If set the value of the variable is is persistent across resets and |
| 59 | // power cycles. Variables without this set cannot be created or modified |
| 60 | // after UEFI boot services are terminated. |
| 61 | AttrNonVolatile Attribute = 1 << iota |
| 62 | // If set allows access to this variable from UEFI boot services. |
| 63 | AttrBootserviceAccess |
| 64 | // If set allows access to this variable from an operating system after |
| 65 | // UEFI boot services are terminated. Variables setting this must also |
| 66 | // set AttrBootserviceAccess. This is automatically taken care of by Write |
| 67 | // in this package. |
| 68 | AttrRuntimeAccess |
| 69 | // Marks a variable as being a hardware error record. See UEFI 2.10 section |
| 70 | // 8.2.8 for more information about this. |
| 71 | AttrHardwareErrorRecord |
| 72 | // Deprecated, should not be used for new variables. |
| 73 | AttrAuthenticatedWriteAccess |
| 74 | // Variable requires special authentication to write. These variables |
| 75 | // cannot be written with this package. |
| 76 | AttrTimeBasedAuthenticatedWriteAccess |
| 77 | // If set in a Write() call, tries to append the data instead of replacing |
| 78 | // it completely. |
| 79 | AttrAppendWrite |
| 80 | // Variable requires special authentication to access and write. These |
| 81 | // variables cannot be accessed with this package. |
| 82 | AttrEnhancedAuthenticatedAccess |
| 83 | ) |
| 84 | |
| 85 | func varPath(scope uuid.UUID, varName string) string { |
| 86 | return fmt.Sprintf("/sys/firmware/efi/efivars/%s-%s", varName, scope.String()) |
Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 87 | } |
| 88 | |
Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame^] | 89 | // Write writes the value of the named variable in the given scope. |
| 90 | func Write(scope uuid.UUID, varName string, attrs Attribute, value []byte) error { |
| 91 | // Write attributes, see @linux//Documentation/filesystems:efivarfs.rst for format |
| 92 | f, err := os.OpenFile(varPath(scope, varName), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) |
Mateusz Zalega | c6c092b | 2021-11-09 13:09:37 +0100 | [diff] [blame] | 93 | if err != nil { |
Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame^] | 94 | e := err |
| 95 | // Unwrap PathError here as we wrap our own parameter message around it |
| 96 | var perr *fs.PathError |
| 97 | if errors.As(err, &perr) { |
| 98 | e = perr.Err |
Mateusz Zalega | 5b60e58 | 2021-11-10 19:57:17 +0100 | [diff] [blame] | 99 | } |
Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame^] | 100 | return fmt.Errorf("writing %q in scope %s: %w", varName, scope, e) |
Mateusz Zalega | 5b60e58 | 2021-11-10 19:57:17 +0100 | [diff] [blame] | 101 | } |
Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame^] | 102 | // Required by UEFI 2.10 Section 8.2.3: |
| 103 | // Runtime access to a data variable implies boot service access. Attributes |
| 104 | // that have EFI_VARIABLE_RUNTIME_ACCESS set must also have |
| 105 | // EFI_VARIABLE_BOOTSERVICE_ACCESS set. The caller is responsible for |
| 106 | // following this rule. |
| 107 | if attrs&AttrRuntimeAccess != 0 { |
| 108 | attrs |= AttrBootserviceAccess |
Mateusz Zalega | 5b60e58 | 2021-11-10 19:57:17 +0100 | [diff] [blame] | 109 | } |
Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame^] | 110 | // Linux wants everything in on write, so assemble an intermediate buffer |
| 111 | buf := make([]byte, len(value)+4) |
| 112 | binary.LittleEndian.PutUint32(buf[:4], uint32(attrs)) |
| 113 | copy(buf[4:], value) |
| 114 | _, err = f.Write(buf) |
| 115 | if err1 := f.Close(); err1 != nil && err == nil { |
| 116 | err = err1 |
Mateusz Zalega | 5b60e58 | 2021-11-10 19:57:17 +0100 | [diff] [blame] | 117 | } |
Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame^] | 118 | return err |
Mateusz Zalega | 5b60e58 | 2021-11-10 19:57:17 +0100 | [diff] [blame] | 119 | } |
| 120 | |
Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame^] | 121 | // Read reads the value of the named variable in the given scope. |
| 122 | func Read(scope uuid.UUID, varName string) ([]byte, Attribute, error) { |
| 123 | val, err := os.ReadFile(varPath(scope, varName)) |
| 124 | if err != nil { |
| 125 | e := err |
| 126 | // Unwrap PathError here as we wrap our own parameter message around it |
| 127 | var perr *fs.PathError |
| 128 | if errors.As(err, &perr) { |
| 129 | e = perr.Err |
| 130 | } |
| 131 | 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] | 132 | } |
Lorenz Brun | ca1cff0 | 2023-06-26 17:52:44 +0200 | [diff] [blame^] | 133 | if len(val) < 4 { |
| 134 | return nil, Attribute(0), fmt.Errorf("reading %q in scope %s: malformed, less than 4 bytes long", varName, scope) |
| 135 | } |
| 136 | return val[4:], Attribute(binary.LittleEndian.Uint32(val[:4])), nil |
| 137 | } |
| 138 | |
| 139 | // List lists all variable names present for a given scope sorted by their names |
| 140 | // in Go's "native" string sort order. |
| 141 | func List(scope uuid.UUID) ([]string, error) { |
| 142 | vars, err := os.ReadDir(Path) |
| 143 | if err != nil { |
| 144 | return nil, fmt.Errorf("failed to list variable directory: %w", err) |
| 145 | } |
| 146 | var outVarNames []string |
| 147 | suffix := fmt.Sprintf("-%v", scope) |
| 148 | for _, v := range vars { |
| 149 | if v.IsDir() { |
| 150 | continue |
| 151 | } |
| 152 | if !strings.HasSuffix(v.Name(), suffix) { |
| 153 | continue |
| 154 | } |
| 155 | outVarNames = append(outVarNames, strings.TrimSuffix(v.Name(), suffix)) |
| 156 | } |
| 157 | return outVarNames, nil |
Mateusz Zalega | 5b60e58 | 2021-11-10 19:57:17 +0100 | [diff] [blame] | 158 | } |