blob: e7511450d3acb6083a5dbd3052b4b2d5ed812c45 [file] [log] [blame]
Mateusz Zalegac6c092b2021-11-09 13:09:37 +01001// 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 Brunca1cff02023-06-26 17:52:44 +020017// 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 Zalegac6c092b2021-11-09 13:09:37 +010021//
Lorenz Brunca1cff02023-06-26 17:52:44 +020022// [1] https://www.kernel.org/doc/html/latest/filesystems/efivarfs.html
Mateusz Zalegac6c092b2021-11-09 13:09:37 +010023package efivarfs
24
25import (
Lorenz Brunca1cff02023-06-26 17:52:44 +020026 "encoding/binary"
27 "errors"
Mateusz Zalegac6c092b2021-11-09 13:09:37 +010028 "fmt"
Lorenz Brunca1cff02023-06-26 17:52:44 +020029 "io/fs"
Mateusz Zalega5b60e582021-11-10 19:57:17 +010030 "os"
Lorenz Brunca1cff02023-06-26 17:52:44 +020031 "strings"
Mateusz Zalegac6c092b2021-11-09 13:09:37 +010032
Lorenz Brunca9cfcf2023-05-02 19:29:14 +020033 "github.com/google/uuid"
Mateusz Zalegac6c092b2021-11-09 13:09:37 +010034 "golang.org/x/text/encoding/unicode"
35)
36
37const (
Lorenz Brunca1cff02023-06-26 17:52:44 +020038 Path = "/sys/firmware/efi/efivars"
39)
40
41var (
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 Zalegac6c092b2021-11-09 13:09:37 +010047)
48
Mateusz Zalega6cefe512021-11-08 18:19:42 +010049// 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.
52var Encoding = unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
53
Lorenz Brunca1cff02023-06-26 17:52:44 +020054// Attribute contains a bitset of EFI variable attributes.
55type Attribute uint32
56
57const (
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
85func varPath(scope uuid.UUID, varName string) string {
86 return fmt.Sprintf("/sys/firmware/efi/efivars/%s-%s", varName, scope.String())
Mateusz Zalegac6c092b2021-11-09 13:09:37 +010087}
88
Lorenz Brunca1cff02023-06-26 17:52:44 +020089// Write writes the value of the named variable in the given scope.
90func 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 Zalegac6c092b2021-11-09 13:09:37 +010093 if err != nil {
Lorenz Brunca1cff02023-06-26 17:52:44 +020094 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 Zalega5b60e582021-11-10 19:57:17 +010099 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200100 return fmt.Errorf("writing %q in scope %s: %w", varName, scope, e)
Mateusz Zalega5b60e582021-11-10 19:57:17 +0100101 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200102 // 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 Zalega5b60e582021-11-10 19:57:17 +0100109 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200110 // 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 Zalega5b60e582021-11-10 19:57:17 +0100117 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200118 return err
Mateusz Zalega5b60e582021-11-10 19:57:17 +0100119}
120
Lorenz Brunca1cff02023-06-26 17:52:44 +0200121// Read reads the value of the named variable in the given scope.
122func 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 Zalega5b60e582021-11-10 19:57:17 +0100132 }
Lorenz Brunca1cff02023-06-26 17:52:44 +0200133 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.
141func 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 Zalega5b60e582021-11-10 19:57:17 +0100158}