| // MIT License |
| // |
| // Copyright (c) 2021 Philippe Voinov (philippevoinov@gmail.com) |
| // Copyright 2021 The Monogon Project Authors. |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a copy |
| // of this software and associated documentation files (the "Software"), to deal |
| // in the Software without restriction, including without limitation the rights |
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| // copies of the Software, and to permit persons to whom the Software is |
| // furnished to do so, subject to the following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included in all |
| // copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| // SOFTWARE. |
| |
| package efivarfs |
| |
| import ( |
| "fmt" |
| "math" |
| "strings" |
| |
| "github.com/google/uuid" |
| "golang.org/x/text/transform" |
| ) |
| |
| // Note on binary format of EFI variables: |
| // This code follows Section 3 "Boot Manager" of version 2.6 of the UEFI Spec: |
| // http://www.uefi.org/sites/default/files/resources/UEFI%20Spec%202_6.pdf |
| // It uses the binary representation from the Linux "efivars" filesystem. |
| // Specifically, all binary data that is marshaled and unmarshaled is preceded by |
| // 4 bytes of "Variable Attributes". |
| // All binary data must have exactly the correct length and may not be padded |
| // with extra bytes while reading or writing. |
| |
| // Note on EFI variable attributes: |
| // This code ignores all EFI variable attributes when reading. |
| // This code always writes variables with the following attributes: |
| // - EFI_VARIABLE_NON_VOLATILE (0x00000001) |
| // - EFI_VARIABLE_BOOTSERVICE_ACCESS (0x00000002) |
| // - EFI_VARIABLE_RUNTIME_ACCESS (0x00000004) |
| const defaultAttrsByte0 uint8 = 7 |
| |
| // BootEntry represents a subset of the contents of a Boot#### EFI variable. |
| type BootEntry struct { |
| Description string // eg. "Linux Boot Manager" |
| Path string // eg. `\EFI\systemd\systemd-bootx64.efi` |
| PartitionGUID uuid.UUID |
| PartitionNumber uint32 // Starts with 1 |
| PartitionStart uint64 // LBA |
| PartitionSize uint64 // LBA |
| } |
| |
| // Marshal generates the binary representation of a BootEntry (EFI_LOAD_OPTION). |
| // Description, DiskGUID and Path must be set. |
| // Attributes of the boot entry (EFI_LOAD_OPTION.Attributes, not the same |
| // as attributes of an EFI variable) are always set to LOAD_OPTION_ACTIVE. |
| func (t *BootEntry) Marshal() ([]byte, error) { |
| if t.Description == "" || |
| t.PartitionGUID.String() == "00000000-0000-0000-0000-000000000000" || |
| t.Path == "" || |
| t.PartitionNumber == 0 || |
| t.PartitionStart == 0 || |
| t.PartitionSize == 0 { |
| return nil, fmt.Errorf("missing field, all are required: %+v", *t) |
| } |
| |
| // EFI_LOAD_OPTION.FilePathList |
| var dp []byte |
| |
| // EFI_LOAD_OPTION.FilePathList[0] |
| dp = append(dp, |
| 0x04, // Type ("Media Device Path") |
| 0x01, // Sub-Type ("Hard Drive") |
| 0x2a, 0x00, // Length (always 42 bytes for this type) |
| ) |
| dp = append32(dp, t.PartitionNumber) |
| dp = append64(dp, t.PartitionStart) |
| dp = append64(dp, t.PartitionSize) |
| // Append the partition GUID in the EFI format. |
| dp = append(dp, MarshalEFIGUID(t.PartitionGUID)...) |
| |
| dp = append(dp, |
| 0x02, // Partition Format ("GUID Partition Table") |
| 0x02, // Signature Type ("GUID signature") |
| ) |
| |
| // EFI_LOAD_OPTION.FilePathList[1] |
| enc := Encoding.NewEncoder() |
| bsp := strings.ReplaceAll(t.Path, "/", "\\") |
| path, _, e := transform.Bytes(enc, []byte(bsp)) |
| if e != nil { |
| return nil, fmt.Errorf("while encoding Path: %v", e) |
| } |
| path = append16(path, 0) // null terminate string |
| filePathLen := len(path) + 4 |
| dp = append(dp, |
| 0x04, // Type ("Media Device Path") |
| 0x04, // Sub-Type ("File Path") |
| ) |
| dp = append16(dp, uint16(filePathLen)) |
| dp = append(dp, path...) |
| |
| // EFI_LOAD_OPTION.FilePathList[2] ("Device Path End Structure") |
| dp = append(dp, |
| 0x7F, // Type ("End of Hardware Device Path") |
| 0xFF, // Sub-Type ("End Entire Device Path") |
| 0x04, 0x00, // Length (always 4 bytes for this type) |
| ) |
| |
| out := []byte{ |
| // EFI variable attributes |
| defaultAttrsByte0, 0x00, 0x00, 0x00, |
| |
| // EFI_LOAD_OPTION.Attributes (only LOAD_OPTION_ACTIVE) |
| 0x01, 0x00, 0x00, 0x00, |
| } |
| |
| // EFI_LOAD_OPTION.FilePathListLength |
| if len(dp) > math.MaxUint16 { |
| // No need to also check for overflows for Path length field explicitly, |
| // since if that overflows, this field will definitely overflow as well. |
| // There is no explicit length field for Description, so no special |
| // handling is required. |
| return nil, fmt.Errorf("variable too large, use shorter strings") |
| } |
| out = append16(out, uint16(len(dp))) |
| |
| // EFI_LOAD_OPTION.Description |
| desc, _, e := transform.Bytes(enc, []byte(t.Description)) |
| if e != nil { |
| return nil, fmt.Errorf("while encoding Description: %v", e) |
| } |
| desc = append16(desc, 0) // null terminate string |
| out = append(out, desc...) |
| |
| // EFI_LOAD_OPTION.FilePathList |
| out = append(out, dp...) |
| |
| // EFI_LOAD_OPTION.OptionalData is always empty |
| |
| return out, nil |
| } |
| |
| // UnmarshalBootEntry loads a BootEntry from its binary representation. |
| // WARNING: UnmarshalBootEntry only loads the Description field. |
| // Everything else is ignored (and not validated if possible) |
| func UnmarshalBootEntry(d []byte) (*BootEntry, error) { |
| descOffset := 4 /* EFI Var Attrs */ + 4 /* EFI_LOAD_OPTION.Attributes */ + 2 /*FilePathListLength*/ |
| if len(d) < descOffset { |
| return nil, fmt.Errorf("too short: %v bytes", len(d)) |
| } |
| descBytes := []byte{} |
| var foundNull bool |
| for i := descOffset; i+1 < len(d); i += 2 { |
| a := d[i] |
| b := d[i+1] |
| if a == 0 && b == 0 { |
| foundNull = true |
| break |
| } |
| descBytes = append(descBytes, a, b) |
| } |
| if !foundNull { |
| return nil, fmt.Errorf("didn't find null terminator for Description") |
| } |
| descDecoded, _, e := transform.Bytes(Encoding.NewDecoder(), descBytes) |
| if e != nil { |
| return nil, fmt.Errorf("while decoding Description: %v", e) |
| } |
| return &BootEntry{Description: string(descDecoded)}, nil |
| } |
| |
| // BootOrder represents the contents of the BootOrder EFI variable. |
| type BootOrder []uint16 |
| |
| // Marshal generates the binary representation of a BootOrder. |
| func (t *BootOrder) Marshal() []byte { |
| out := []byte{defaultAttrsByte0, 0x00, 0x00, 0x00} |
| for _, v := range *t { |
| out = append16(out, v) |
| } |
| return out |
| } |
| |
| // UnmarshalBootOrder loads a BootOrder from its binary representation. |
| func UnmarshalBootOrder(d []byte) (*BootOrder, error) { |
| if len(d) < 4 || len(d)%2 != 0 { |
| return nil, fmt.Errorf("invalid length: %v bytes", len(d)) |
| } |
| l := (len(d) - 4) / 2 |
| out := make(BootOrder, l) |
| for i := 0; i < l; i++ { |
| out[i] = uint16(d[4+2*i]) | uint16(d[4+2*i+1])<<8 |
| } |
| return &out, nil |
| } |
| |
| func append16(d []byte, v uint16) []byte { |
| return append(d, |
| byte(v&0xFF), |
| byte(v>>8&0xFF), |
| ) |
| } |
| |
| func append32(d []byte, v uint32) []byte { |
| return append(d, |
| byte(v&0xFF), |
| byte(v>>8&0xFF), |
| byte(v>>16&0xFF), |
| byte(v>>24&0xFF), |
| ) |
| } |
| |
| func append64(d []byte, v uint64) []byte { |
| return append(d, |
| byte(v&0xFF), |
| byte(v>>8&0xFF), |
| byte(v>>16&0xFF), |
| byte(v>>24&0xFF), |
| byte(v>>32&0xFF), |
| byte(v>>40&0xFF), |
| byte(v>>48&0xFF), |
| byte(v>>56&0xFF), |
| ) |
| } |