| // Copyright 2020 The Monogon Project Authors. | 
 | // | 
 | // SPDX-License-Identifier: Apache-2.0 | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //     http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 |  | 
 | // Package efivarfs provides functions to read and manipulate UEFI runtime | 
 | // variables. It uses Linux's efivarfs [1] to access the variables and all | 
 | // functions generally require that this is mounted at | 
 | // "/sys/firmware/efi/efivars". | 
 | // | 
 | // [1] https://www.kernel.org/doc/html/latest/filesystems/efivarfs.html | 
 | package efivarfs | 
 |  | 
 | import ( | 
 | 	"encoding/binary" | 
 | 	"errors" | 
 | 	"fmt" | 
 | 	"io/fs" | 
 | 	"os" | 
 | 	"strings" | 
 |  | 
 | 	"github.com/google/uuid" | 
 | 	"golang.org/x/text/encoding/unicode" | 
 | ) | 
 |  | 
 | const ( | 
 | 	Path = "/sys/firmware/efi/efivars" | 
 | ) | 
 |  | 
 | var ( | 
 | 	// ScopeGlobal is the scope of variables defined by the EFI specification | 
 | 	// itself. | 
 | 	ScopeGlobal = uuid.MustParse("8be4df61-93ca-11d2-aa0d-00e098032b8c") | 
 | 	// ScopeSystemd is the scope of variables defined by Systemd/bootspec. | 
 | 	ScopeSystemd = uuid.MustParse("4a67b082-0a4c-41cf-b6c7-440b29bb8c4f") | 
 | ) | 
 |  | 
 | // Encoding defines the Unicode encoding used by UEFI, which is UCS-2 Little | 
 | // Endian. For BMP characters UTF-16 is equivalent to UCS-2. See the UEFI | 
 | // Spec 2.9, Sections 33.2.6 and 1.8.1. | 
 | var Encoding = unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM) | 
 |  | 
 | // Attribute contains a bitset of EFI variable attributes. | 
 | type Attribute uint32 | 
 |  | 
 | const ( | 
 | 	// If set the value of the variable is is persistent across resets and | 
 | 	// power cycles. Variables without this set cannot be created or modified | 
 | 	// after UEFI boot services are terminated. | 
 | 	AttrNonVolatile Attribute = 1 << iota | 
 | 	// If set allows access to this variable from UEFI boot services. | 
 | 	AttrBootserviceAccess | 
 | 	// If set allows access to this variable from an operating system after | 
 | 	// UEFI boot services are terminated. Variables setting this must also | 
 | 	// set AttrBootserviceAccess. This is automatically taken care of by Write | 
 | 	// in this package. | 
 | 	AttrRuntimeAccess | 
 | 	// Marks a variable as being a hardware error record. See UEFI 2.10 section | 
 | 	// 8.2.8 for more information about this. | 
 | 	AttrHardwareErrorRecord | 
 | 	// Deprecated, should not be used for new variables. | 
 | 	AttrAuthenticatedWriteAccess | 
 | 	// Variable requires special authentication to write. These variables | 
 | 	// cannot be written with this package. | 
 | 	AttrTimeBasedAuthenticatedWriteAccess | 
 | 	// If set in a Write() call, tries to append the data instead of replacing | 
 | 	// it completely. | 
 | 	AttrAppendWrite | 
 | 	// Variable requires special authentication to access and write. These | 
 | 	// variables cannot be accessed with this package. | 
 | 	AttrEnhancedAuthenticatedAccess | 
 | ) | 
 |  | 
 | func varPath(scope uuid.UUID, varName string) string { | 
 | 	return fmt.Sprintf("/sys/firmware/efi/efivars/%s-%s", varName, scope.String()) | 
 | } | 
 |  | 
 | // Write writes the value of the named variable in the given scope. | 
 | func Write(scope uuid.UUID, varName string, attrs Attribute, value []byte) error { | 
 | 	// Write attributes, see @linux//Documentation/filesystems:efivarfs.rst for format | 
 | 	f, err := os.OpenFile(varPath(scope, varName), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) | 
 | 	if err != nil { | 
 | 		e := err | 
 | 		// Unwrap PathError here as we wrap our own parameter message around it | 
 | 		var perr *fs.PathError | 
 | 		if errors.As(err, &perr) { | 
 | 			e = perr.Err | 
 | 		} | 
 | 		return fmt.Errorf("writing %q in scope %s: %w", varName, scope, e) | 
 | 	} | 
 | 	// Required by UEFI 2.10 Section 8.2.3: | 
 | 	// Runtime access to a data variable implies boot service access. Attributes | 
 | 	// that have EFI_VARIABLE_RUNTIME_ACCESS set must also have | 
 | 	// EFI_VARIABLE_BOOTSERVICE_ACCESS set. The caller is responsible for | 
 | 	// following this rule. | 
 | 	if attrs&AttrRuntimeAccess != 0 { | 
 | 		attrs |= AttrBootserviceAccess | 
 | 	} | 
 | 	// Linux wants everything in on write, so assemble an intermediate buffer | 
 | 	buf := make([]byte, len(value)+4) | 
 | 	binary.LittleEndian.PutUint32(buf[:4], uint32(attrs)) | 
 | 	copy(buf[4:], value) | 
 | 	_, err = f.Write(buf) | 
 | 	if err1 := f.Close(); err1 != nil && err == nil { | 
 | 		err = err1 | 
 | 	} | 
 | 	return err | 
 | } | 
 |  | 
 | // Read reads the value of the named variable in the given scope. | 
 | func Read(scope uuid.UUID, varName string) ([]byte, Attribute, error) { | 
 | 	val, err := os.ReadFile(varPath(scope, varName)) | 
 | 	if err != nil { | 
 | 		e := err | 
 | 		// Unwrap PathError here as we wrap our own parameter message around it | 
 | 		var perr *fs.PathError | 
 | 		if errors.As(err, &perr) { | 
 | 			e = perr.Err | 
 | 		} | 
 | 		return nil, Attribute(0), fmt.Errorf("reading %q in scope %s: %w", varName, scope, e) | 
 | 	} | 
 | 	if len(val) < 4 { | 
 | 		return nil, Attribute(0), fmt.Errorf("reading %q in scope %s: malformed, less than 4 bytes long", varName, scope) | 
 | 	} | 
 | 	return val[4:], Attribute(binary.LittleEndian.Uint32(val[:4])), nil | 
 | } | 
 |  | 
 | // List lists all variable names present for a given scope sorted by their names | 
 | // in Go's "native" string sort order. | 
 | func List(scope uuid.UUID) ([]string, error) { | 
 | 	vars, err := os.ReadDir(Path) | 
 | 	if err != nil { | 
 | 		return nil, fmt.Errorf("failed to list variable directory: %w", err) | 
 | 	} | 
 | 	var outVarNames []string | 
 | 	suffix := fmt.Sprintf("-%v", scope) | 
 | 	for _, v := range vars { | 
 | 		if v.IsDir() { | 
 | 			continue | 
 | 		} | 
 | 		if !strings.HasSuffix(v.Name(), suffix) { | 
 | 			continue | 
 | 		} | 
 | 		outVarNames = append(outVarNames, strings.TrimSuffix(v.Name(), suffix)) | 
 | 	} | 
 | 	return outVarNames, nil | 
 | } |