shared/uefi: Add some OVMF formatting primitives

This also fixes a few oddities in the code.
This leaves boot entries, device paths, and keyboard shortcuts
unformatted, as those require too much time to consider including in
Incus 7.3.

Signed-off-by: Benjamin Somers <benjamin.somers@imt-atlantique.fr>
This commit is contained in:
Benjamin Somers 2026-07-29 19:14:47 +00:00 committed by Stéphane Graber
parent 5eaad262c2
commit 65dfe674f3
No known key found for this signature in database
GPG Key ID: C638974D64792D67
8 changed files with 630 additions and 301 deletions

View File

@ -1,12 +1,13 @@
package uefi
import (
"encoding/json"
"fmt"
"strconv"
"strings"
)
// WrapDP wraps a device path dissector.
// wrapDP wraps a device path dissector.
func wrapDP(f func(*reader, uint8) (string, error)) func(uint8, []byte) (string, error) {
return func(subtype uint8, b []byte) (string, error) {
r := newReader(b)
@ -1353,16 +1354,22 @@ func devicePaths(b []byte) ([][]string, error) {
return paths, nil
}
// devicePaths dissects a device path structure.
func devicePath(b []byte) ([]string, error) {
paths, err := devicePaths(b)
if err != nil {
return nil, err
}
// devicePath dissects a device path structure.
// TODO: Implement variable formatting.
var devicePath = dissector{
dissect: func(b []byte) (any, error) {
paths, err := devicePaths(b)
if err != nil {
return nil, err
}
if len(paths) != 1 {
return nil, errUnexpectedData
}
if len(paths) != 1 {
return nil, errUnexpectedData
}
return paths[0], nil
return paths[0], nil
},
format: func(json.RawMessage) ([]byte, error) {
return nil, errNotImplemented
},
}

View File

@ -58,6 +58,7 @@ var scanCodes = map[uint16]string{
}
// keyboard dissects keyboard data.
// TODO: Implement variable formatting.
var keyboard = wrap(func(r *reader) (any, error) {
keyData, err := r.readU32()
if err != nil {

View File

@ -2,43 +2,77 @@ package uefi
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
)
// WrapDP wraps a variable dissector.
func wrap[T any](f func(*reader) (T, error)) func([]byte) (any, error) {
return func(b []byte) (any, error) {
r := newReader(b)
v, err := f(r)
if err != nil {
return nil, err
}
type dissector struct {
dissect func([]byte) (any, error)
format func(json.RawMessage) ([]byte, error)
}
if !r.eof() {
return nil, errUnexpectedData
}
// wrap wraps a variable dissector.
func wrap[T any](f func(*reader) (T, error), gs ...func(*writer, T) error) dissector {
g := func(*writer, T) error {
return errNotImplemented
}
return v, nil
if len(gs) > 0 {
g = gs[0]
}
return dissector{
dissect: func(b []byte) (any, error) {
r := newReader(b)
v, err := f(r)
if err != nil {
return nil, err
}
if !r.eof() {
return nil, errUnexpectedData
}
return v, nil
},
format: func(in json.RawMessage) ([]byte, error) {
w := newWriter()
var v T
err := json.Unmarshal(in, &v)
if err != nil {
return nil, err
}
err = g(w, v)
if err != nil {
return nil, err
}
return w.data, nil
},
}
}
// b8 dissects 8-bit booleans.
var b8 = wrap((*reader).readB8)
var b8 = wrap((*reader).readB8, (*writer).writeB8)
// u16 dissects 16-bit integers.
var u16 = wrap((*reader).readU16)
var u16 = wrap((*reader).readU16, (*writer).writeU16)
// u32 dissects 32-bit integers.
var u32 = wrap((*reader).readU32)
var u32 = wrap((*reader).readU32, (*writer).writeU32)
// zn8 dissects NUL-terminated UTF8 strings.
var zn8 = wrap(func(r *reader) (string, error) { return r.readZn8() })
var zn8 = wrap(func(r *reader) (string, error) { return r.readZn8() }, func(w *writer, v string) error { return w.writeZn8(v) })
// z8 dissects UTF8 strings.
var z8 = wrap(func(r *reader) (string, error) { return r.readZ8(r.rem()) })
var z8 = wrap(func(r *reader) (string, error) { return r.readZ8(r.rem()) }, func(w *writer, v string) error { return w.writeZ8(v) })
// bootOrder dissects `BootOrder` and `DriverOrder` variables.
func bootOrder(prefix string, b []byte) (any, error) {
func bootOrder(prefix string) dissector {
return wrap(func(r *reader) ([]string, error) {
rem := r.rem()
if rem%2 != 0 {
@ -56,11 +90,31 @@ func bootOrder(prefix string, b []byte) (any, error) {
}
return entries, nil
})(b)
}, func(w *writer, v []string) error {
for _, entry := range v {
suffix, ok := strings.CutPrefix(entry, prefix)
if !ok || len(suffix) != 4 {
return fmt.Errorf("Entries must be of the form %s####", prefix)
}
n, err := strconv.ParseUint(suffix, 16, 16)
if err != nil {
return fmt.Errorf("Invalid entry ID: %s", suffix)
}
err = w.writeU16(uint16(n))
if err != nil {
return err
}
}
return nil
})
}
// boot dissects `Boot####`, `Driver####`, `SysPrep####`, `OsRecovery####` and
// `PlatformRecovery####` variables.
// TODO: Implement variable formatting.
var boot = wrap(func(r *reader) (any, error) {
attrs, err := r.readU32()
if err != nil {
@ -122,77 +176,54 @@ var boot = wrap(func(r *reader) (any, error) {
}, nil
})
type eslEntry struct {
Owner string `json:"owner"`
Data []byte `json:"data"`
}
type eslNode struct {
Type string `json:"type"`
Header []byte `json:"header,omitempty"`
Entries []eslEntry `json:"entries"`
}
// esl dissects EFI signature lists.
func esl(data []byte) (any, error) {
type sigData struct {
Owner string `json:"owner"`
Data []byte `json:"data"`
}
type sigList struct {
Type string `json:"type"`
Header []byte `json:"header,omitempty"`
Entries []sigData `json:"entries"`
}
var db []sigList
r := newReader(data)
var esl = wrap(func(r *reader) ([]eslNode, error) {
db := []eslNode{}
for !r.eof() {
start := r.pos()
if start > len(data)-28 {
sigGUID, err := r.readGUID()
if err != nil {
return nil, err
}
typeStr, ok := guidSigs[sigGUID]
if !ok {
return nil, errUnexpectedData
}
sigType, err := r.readGUID()
if err != nil {
return nil, err
}
typeStr, err := parseSigType(sigType)
if err != nil {
return nil, err
}
listSize, err := r.readU32()
if err != nil {
return nil, err
}
end := start + int(listSize)
if end > len(data) {
return nil, errUnexpectedData
}
headerSize, err := r.readU32()
if err != nil {
return nil, err
}
if listSize < 28+headerSize {
return nil, errUnexpectedData
}
sigSize, err := r.readU32()
if err != nil {
return nil, err
}
if sigSize < 16 {
return nil, errUnexpectedData
}
header, err := r.read(int(headerSize))
if err != nil {
return nil, err
}
sigBytes := end - r.pos()
if sigBytes%int(sigSize) != 0 {
return nil, errUnexpectedData
}
lst := sigList{Type: typeStr, Header: header}
for r.pos() < end {
lst := eslNode{Type: typeStr, Header: header}
for r.pos()-start < int(listSize) {
owner, err := r.readGUID()
if err != nil {
return nil, err
@ -203,17 +234,86 @@ func esl(data []byte) (any, error) {
return nil, err
}
lst.Entries = append(lst.Entries, sigData{Owner: owner, Data: body})
lst.Entries = append(lst.Entries, eslEntry{Owner: owner, Data: body})
}
db = append(db, lst)
}
return db, nil
}, func(w *writer, v []eslNode) error {
for _, node := range v {
start := w.size()
sigGUID, ok := sigGUIDs[node.Type]
if !ok {
return fmt.Errorf("Unknown signature type %s", node.Type)
}
err := w.writeGUID(sigGUID)
if err != nil {
return err
}
listSizeIdx := w.size()
// To be filled later.
err = w.writeU32(0)
if err != nil {
return err
}
err = w.writeU32(uint32(len(node.Header)))
if err != nil {
return err
}
// We guess the signature size based on the first entry.
sigSize := uint32(0)
if len(node.Entries) > 0 {
sigSize = uint32(16 + len(node.Entries[0].Data))
}
err = w.writeU32(sigSize)
if err != nil {
return err
}
err = w.write(node.Header)
if err != nil {
return err
}
for _, entry := range node.Entries {
err = w.writeGUID(entry.Owner)
if err != nil {
return err
}
if len(entry.Data) != int(sigSize)-16 {
return errors.New("Inhomogeneous signature sizes")
}
err = w.write(entry.Data)
if err != nil {
return err
}
}
err = w.writeU32At(uint32(w.size()-start), listSizeIdx)
if err != nil {
return err
}
}
return nil
})
type errorFlagType struct {
UserError bool `json:"user_error"`
SystemError bool `json:"system_error"`
}
// errorFlag dissects `VarErrorFlag` variables.
var errorFlag = wrap(func(r *reader) (any, error) {
var errorFlag = wrap(func(r *reader) (*errorFlagType, error) {
v, err := r.readU8()
if err != nil {
return nil, err
@ -223,30 +323,56 @@ var errorFlag = wrap(func(r *reader) (any, error) {
return nil, errUnexpectedData
}
return struct {
UserError bool `json:"user_error"`
SystemError bool `json:"system_error"`
}{
return &errorFlagType{
UserError: v&0x01 == 0,
SystemError: v&0x10 == 0,
}, nil
}, func(w *writer, v *errorFlagType) error {
b := uint8(0xFF)
if v.UserError {
b &^= 0x01
}
if v.SystemError {
b &^= 0x10
}
return w.writeU8(b)
})
// attemptOrder dissects `InitialAttemptOrder` variables.
func attemptOrder(b []byte) ([]string, error) {
r := newReader(b)
resp := make([]string, len(b))
for i := 0; !r.eof(); i += 1 {
var attemptOrder = wrap(func(r *reader) ([]string, error) {
resp := []string{}
for !r.eof() {
v, err := r.readU8()
if err != nil {
return nil, err
}
resp[i] = fmt.Sprintf("Attempt %d", v)
resp = append(resp, fmt.Sprintf("Attempt %d", v))
}
return resp, nil
}
}, func(w *writer, v []string) error {
for _, entry := range v {
suffix, ok := strings.CutPrefix(entry, "Attempt ")
if !ok {
return errors.New("Entries must start with “Attempt ”")
}
n, err := strconv.ParseUint(suffix, 10, 8)
if err != nil {
return fmt.Errorf("Invalid entry ID: %s", suffix)
}
err = w.writeU8(uint8(n))
if err != nil {
return err
}
}
return nil
})
// tpmVersion dissects `TCG2_CONFIGURATION` and `TCG2_DEVICE_DETECTION` variables.
var tpmVersion = wrap(func(r *reader) (string, error) {
@ -265,10 +391,29 @@ var tpmVersion = wrap(func(r *reader) (string, error) {
default:
return "", errUnexpectedData
}
}, func(w *writer, v string) error {
var b uint8
switch v {
case "none", "0":
b = 0
case "1.2", "1":
b = 1
case "2.0", "2":
b = 2
default:
return fmt.Errorf("Unexpected version %s, expected one of none, 1.2, or 2.0", v)
}
return w.writeU8(b)
})
type tcg2VersionType struct {
PPIVersion string `json:"ppi_version"`
ACPITableRevision uint8 `json:"acpi_table_revision"`
}
// tcg2Version dissects `TCG2_VERSION` variables.
var tcg2Version = wrap(func(r *reader) (any, error) {
var tcg2Version = wrap(func(r *reader) (*tcg2VersionType, error) {
ppiVersion, err := r.readZn8(8)
if err != nil {
return nil, err
@ -280,38 +425,55 @@ var tcg2Version = wrap(func(r *reader) (any, error) {
}
// Padding
_, err = r.read(7)
err = r.skip(7)
if err != nil {
return nil, err
}
return struct {
PPIVersion string `json:"ppi_version"`
ACPITableRevision uint8 `json:"acpi_table_revision"`
}{
return &tcg2VersionType{
PPIVersion: ppiVersion,
ACPITableRevision: acpiTableRevision,
}, nil
}, func(w *writer, v *tcg2VersionType) error {
err := w.writeZn8(v.PPIVersion, 8)
if err != nil {
return err
}
err = w.writeU8(v.ACPITableRevision)
if err != nil {
return err
}
// Padding
err = w.skip(7)
if err != nil {
return err
}
return nil
})
type osIndicationsType struct {
BootToFWUI bool `json:"boot_to_fw_ui"`
TimestampRevocation bool `json:"timestamp_revocation"`
FileCapsuleDeliverySupported bool `json:"file_capsule_delivery_supported"`
FMPCapsuleSupported bool `json:"fmp_capsule_supported"`
CapsuleResultVarSupported bool `json:"capsule_result_var_supported"`
StartOSRecovery bool `json:"start_os_recovery"`
StartPlatformRecovery bool `json:"start_platform_recovery"`
JSONConfigDataRefresh bool `json:"json_config_data_refresh"`
Reserved uint64 `json:"reserved,omitempty"`
}
// osIndications dissects `OsIndications` variables.
var osIndications = wrap(func(r *reader) (any, error) {
var osIndications = wrap(func(r *reader) (*osIndicationsType, error) {
v, err := r.readU64()
if err != nil {
return nil, err
}
return struct {
BootToFWUI bool `json:"boot_to_fw_ui"`
TimestampRevocation bool `json:"timestamp_revocation"`
FileCapsuleDeliverySupported bool `json:"file_capsule_delivery_supported"`
FMPCapsuleSupported bool `json:"fmp_capsule_supported"`
CapsuleResultVarSupported bool `json:"capsule_result_var_supported"`
StartOSRecovery bool `json:"start_os_recovery"`
StartPlatformRecovery bool `json:"start_platform_recovery"`
JSONConfigDataRefresh bool `json:"json_config_data_refresh"`
Reserved uint64 `json:"reserved,omitempty"`
}{
return &osIndicationsType{
BootToFWUI: v&0x0000_0000_0000_0001 != 0,
TimestampRevocation: v&0x0000_0000_0000_0002 != 0,
FileCapsuleDeliverySupported: v&0x0000_0000_0000_0004 != 0,
@ -322,6 +484,46 @@ var osIndications = wrap(func(r *reader) (any, error) {
JSONConfigDataRefresh: v&0x0000_0000_0000_0080 != 0,
Reserved: v & 0xFFFF_FFFF_FFFF_FF00,
}, nil
}, func(w *writer, v *osIndicationsType) error {
var b uint64
if v.Reserved&0x0000_0000_0000_00FF > 0 {
return errors.New("The last 8 bits of the reserved field must be unset")
}
if v.BootToFWUI {
b |= 0x0000_0000_0000_0001
}
if v.TimestampRevocation {
b |= 0x0000_0000_0000_0002
}
if v.FileCapsuleDeliverySupported {
b |= 0x0000_0000_0000_0004
}
if v.FMPCapsuleSupported {
b |= 0x0000_0000_0000_0008
}
if v.CapsuleResultVarSupported {
b |= 0x0000_0000_0000_0010
}
if v.StartOSRecovery {
b |= 0x0000_0000_0000_0020
}
if v.StartPlatformRecovery {
b |= 0x0000_0000_0000_0040
}
if v.JSONConfigDataRefresh {
b |= 0x0000_0000_0000_0080
}
b |= v.Reserved
return w.writeU64(b)
})
// morControlLock dissects `MemoryOverwriteRequestControlLock` variables.
@ -341,34 +543,66 @@ var morControlLock = wrap(func(r *reader) (string, error) {
default:
return "", errUnexpectedData
}
})
// morControl dissects `MemoryOverwriteRequestControl` variables.
var morControl = wrap(func(r *reader) (any, error) {
v, err := r.readU8()
if err != nil {
return "", err
}, func(w *writer, v string) error {
var b uint8
switch v {
case "unlocked", "0":
b = 0
case "locked_without_key", "1":
b = 1
case "locked_with_key", "2":
b = 2
default:
return fmt.Errorf("Unexpected lock value %s, expected one of unlock, locked_without_key, or locked_with_key", v)
}
return struct {
ClearMemory bool `json:"clear_memory"`
DisableAutoDetect bool `json:"disable_autodetect"`
Reserved uint8 `json:"reserved,omitempty"`
}{
return w.writeU8(b)
})
type morControlType struct {
ClearMemory bool `json:"clear_memory"`
DisableAutoDetect bool `json:"disable_autodetect"`
Reserved uint8 `json:"reserved,omitempty"`
}
// morControl dissects `MemoryOverwriteRequestControl` variables.
var morControl = wrap(func(r *reader) (*morControlType, error) {
v, err := r.readU8()
if err != nil {
return nil, err
}
return &morControlType{
ClearMemory: v&0x01 != 0,
DisableAutoDetect: v&0x10 != 0,
Reserved: v & 0xEE,
}, nil
})
// certDB dissects `certdb` variables.
var certDB = wrap(func(r *reader) (any, error) {
type certDBEntry struct {
Name string
GUID string
Digest []byte
}, func(w *writer, v *morControlType) error {
var b uint8
if v.Reserved&0x11 > 0 {
return errors.New("Bits 0x01 and 0x10 of the reserved field must be unset")
}
if v.ClearMemory {
b |= 0x01
}
if v.DisableAutoDetect {
b |= 0x10
}
b |= v.Reserved
return w.writeU8(b)
})
type certDBEntry struct {
Name string `json:"name"`
GUID string `json:"guid"`
Digest []byte `json:"digest"`
}
// certDB dissects `certdb` variables.
var certDB = wrap(func(r *reader) ([]certDBEntry, error) {
size, err := r.readU32()
if err != nil {
return nil, err
@ -378,14 +612,15 @@ var certDB = wrap(func(r *reader) (any, error) {
return nil, errUnexpectedData
}
var db []certDBEntry
db := []certDBEntry{}
for !r.eof() {
start := r.pos()
guid, err := r.readGUID()
if err != nil {
return nil, err
}
err = r.skip(4)
nodeSize, err := r.readU32()
if err != nil {
return nil, err
}
@ -410,8 +645,52 @@ var certDB = wrap(func(r *reader) (any, error) {
return nil, err
}
if nodeSize != uint32(r.pos()-start) {
return nil, errUnexpectedData
}
db = append(db, certDBEntry{Name: name, GUID: guid, Digest: digest})
}
return db, nil
}, func(w *writer, v []certDBEntry) error {
// To be filled later.
err := w.writeU32(0)
if err != nil {
return err
}
for _, entry := range v {
err = w.writeGUID(entry.GUID)
if err != nil {
return err
}
err = w.writeU32(uint32(28 + 2*len(entry.Name) + len(entry.Digest)))
if err != nil {
return err
}
err = w.writeU32(uint32(len(entry.Name)))
if err != nil {
return err
}
err = w.writeU32(uint32(len(entry.Digest)))
if err != nil {
return err
}
err = w.writeZ16(entry.Name)
if err != nil {
return err
}
err = w.write(entry.Digest)
if err != nil {
return err
}
}
return w.writeU32At(uint32(w.size()), 0)
})

View File

@ -127,6 +127,26 @@ var guidAliases = map[string]string{
"bmharddrivebootvariable": BmHardDriveBootVariableGuid,
}
var guidSigs = map[string]string{
EfiCertPkcs7Guid: "pkcs7",
EfiCertRsa2048Guid: "rsa2048",
EfiCertRsa2048Sha1Guid: "rsa2048-sha1",
EfiCertRsa2048Sha256Guid: "rsa2048-sha256",
EfiCertSha1Guid: "sha1",
EfiCertSha224Guid: "sha224",
EfiCertSha256Guid: "sha256",
EfiCertSha384Guid: "sha384",
EfiCertSha512Guid: "sha512",
EfiCertSm3Guid: "sm3",
EfiCertX509Guid: "x509",
EfiCertX509Sha256Guid: "x509-sha256",
EfiCertX509Sha384Guid: "x509-sha384",
EfiCertX509Sha512Guid: "x509-sha512",
EfiCertX509Sm3Guid: "x509-sm3",
}
var sigGUIDs = make(map[string]string, len(guidSigs))
// normalizeGUIDName transforms a GUID name into something easier for us to handle and more
// forgiving to the users.
func normalizeGUIDName(s string) string {
@ -142,6 +162,10 @@ func init() {
for g, name := range guidNames {
guidAliases[normalizeGUIDName(name)] = g
}
for g, sigType := range guidSigs {
sigGUIDs[sigType] = g
}
}
// GUIDName returns a familiar symbolic name for a known GUID, or the GUID itself.

View File

@ -426,12 +426,32 @@ func (w *writer) size() int {
return len(w.data)
}
// skip skips bytes.
func (w *writer) skip(n int) error {
if n < 0 {
return errUnexpectedData
}
return w.write(make([]byte, n))
}
// write writes raw bytes.
func (w *writer) write(b []byte) error {
w.data = append(w.data, b...)
return nil
}
// writeB8 writes an 8-bit boolean.
func (w *writer) writeB8(v bool) error {
var b byte
if v {
b = 1
}
w.data = append(w.data, b)
return nil
}
// writeU8 writes an 8-bit unsigned integer.
func (w *writer) writeU8(v uint8) error {
w.data = append(w.data, v)
@ -460,6 +480,12 @@ func (w *writer) writeU32(v uint32) error {
return nil
}
// writeU32At writes a 32-bit unsigned integer at the given position.
func (w *writer) writeU32At(v uint32, pos int) error {
binary.LittleEndian.PutUint32(w.data[pos:], v)
return nil
}
// writeU64 writes a 64-bit unsigned integer.
func (w *writer) writeU64(v uint64) error {
b := make([]byte, 8)
@ -468,7 +494,7 @@ func (w *writer) writeU64(v uint64) error {
return nil
}
// writeGUID reads a GUID.
// writeGUID writes a GUID.
func (w *writer) writeGUID(guid string) error {
b, err := hex.DecodeString(strings.ReplaceAll(guid, "-", ""))
if err != nil {
@ -483,6 +509,10 @@ func (w *writer) writeGUID(guid string) error {
func (w *writer) writeZ8(s string, n ...int) error {
size := len(s)
if len(n) > 0 {
if n[0] < size {
return fmt.Errorf("UTF8 string %q cannot fit in %d bytes", s, n[0])
}
size = n[0]
}
@ -492,14 +522,14 @@ func (w *writer) writeZ8(s string, n ...int) error {
return nil
}
// writeZ16 writes an UTF16 string.
func (w *writer) writeZ16(s string, n ...int) error {
size := len(s) * 2
if len(n) > 0 {
size = n[0] * 2
}
// writeZn8 writes a NUL-terminated ASCII/UTF8 string.
func (w *writer) writeZn8(s string, n ...int) error {
return w.writeZ8(s+"\x00", n...)
}
b := make([]byte, size)
// writeZ16 writes an UTF16 string.
func (w *writer) writeZ16(s string) error {
b := make([]byte, len(s)*2)
for i, c := range utf16.Encode([]rune(s)) {
b[i*2] = byte(c)
b[i*2+1] = byte(c >> 8)
@ -510,7 +540,7 @@ func (w *writer) writeZ16(s string, n ...int) error {
}
// writeZ16 writes a NUL-terminated UTF16 string.
func (w *writer) writeZn16(s string, n ...int) error {
func (w *writer) writeZn16(s string) error {
return w.writeZ16(s + "\x00")
}

View File

@ -231,7 +231,7 @@ func ParseNVRAM(data []byte) (*Store, error) {
}
if state == 0x3f {
varPut := api.InstanceNVRAMVariablePut{Attributes: parseAttributes(rawAttributes), Timestamp: timestamp}
varPut := api.InstanceNVRAMVariablePut{Attributes: ParseAttributes(rawAttributes), Timestamp: timestamp}
v := api.InstanceNVRAMVariable{InstanceNVRAMVariablePut: varPut, Binary: data}
_, ok := s.Vars[guid]
if !ok {

View File

@ -10,6 +10,9 @@ import (
// errUnexpectedData is a very generic error returned whenever something fails if the parser.
var errUnexpectedData = errors.New("Unexpected data")
// errNotImplemented is an error returned whenever part of a dissector is not implemented.
var errNotImplemented = errors.New("Not implemented")
// formatGUID formats a GUID.
func formatGUID(guid []byte) string {
return fmt.Sprintf("%08x-%04x-%04x-%x-%x", binary.LittleEndian.Uint32(guid[0:4]), binary.LittleEndian.Uint16(guid[4:6]), binary.LittleEndian.Uint16(guid[6:8]), guid[8:10], guid[10:16])
@ -44,8 +47,8 @@ func formatIP6(ip6 []byte, port ...uint16) string {
return fmt.Sprintf("%s:%d", ip, p)
}
// parseAttributes parses UEFI variable attributes and formats them.
func parseAttributes(rawAttributes uint32) []string {
// ParseAttributes parses UEFI variable attributes and formats them.
func ParseAttributes(rawAttributes uint32) []string {
attributes := []string{}
if rawAttributes&0x0000_0001 != 0 {
@ -111,44 +114,6 @@ func dumpAttributes(attributes []string) uint32 {
return rawAttributes
}
// parseSigType parses signature GUIDs and formats them.
func parseSigType(sigType string) (string, error) {
switch sigType {
case EfiCertPkcs7Guid:
return "pkcs7", nil
case EfiCertRsa2048Guid:
return "rsa2048", nil
case EfiCertRsa2048Sha1Guid:
return "rsa2048-sha1", nil
case EfiCertRsa2048Sha256Guid:
return "rsa2048-sha256", nil
case EfiCertSha1Guid:
return "sha1", nil
case EfiCertSha224Guid:
return "sha224", nil
case EfiCertSha256Guid:
return "sha256", nil
case EfiCertSha384Guid:
return "sha384", nil
case EfiCertSha512Guid:
return "sha512", nil
case EfiCertSm3Guid:
return "sm3", nil
case EfiCertX509Guid:
return "x509", nil
case EfiCertX509Sha256Guid:
return "x509-sha256", nil
case EfiCertX509Sha384Guid:
return "x509-sha384", nil
case EfiCertX509Sha512Guid:
return "x509-sha512", nil
case EfiCertX509Sm3Guid:
return "x509-sm3", nil
default:
return "", errUnexpectedData
}
}
// csum16 computes a 16-bit checksum.
func csum16(b []byte) uint16 {
var sum uint16

View File

@ -1,140 +1,163 @@
package uefi
import (
"encoding/json"
"fmt"
"github.com/lxc/incus/v7/shared/api"
)
// Dissect dissects an UEFI variable.
func Dissect(v *api.InstanceNVRAMVariable, guid string, name string) error {
dissected, err := func() (any, error) {
switch guid {
case EfiGlobalVariableGuid:
hasFourDigits := false
n := len(name)
if n > 4 {
hasFourDigits = true
for _, c := range name[n-4:] {
if c < '0' || c > '9' {
hasFourDigits = false
break
}
func getDissector(guid string, name string) dissector {
switch guid {
case EfiGlobalVariableGuid:
hasFourDigits := false
n := len(name)
if n > 4 {
hasFourDigits = true
for _, c := range name[n-4:] {
if c < '0' || c > '9' {
hasFourDigits = false
break
}
}
if hasFourDigits {
switch name[:n-4] {
case "Boot", "Driver", "SysPrep", "OsRecovery", "PlatformRecovery":
return boot(v.Binary)
case "Key":
return keyboard(v.Binary)
}
}
switch name {
case "BootOrder":
return bootOrder("Boot", v.Binary)
case "ConIn", "ConOut", "ErrOut":
return devicePath(v.Binary)
case "DriverOrder":
return bootOrder("Driver", v.Binary)
case "KEK", "PK":
return esl(v.Binary)
case "Lang", "PlatformLang":
return zn8(v.Binary)
case "OsIndications":
return osIndications(v.Binary)
case "Timeout":
return u16(v.Binary)
}
case ShimLockGuid:
switch name {
case "MokList":
return esl(v.Binary)
case "SbatLevel":
return z8(v.Binary)
}
case EfiVendorKeysNvGuid:
switch name {
case "VendorKeysNv":
return b8(v.Binary)
}
case EfiCustomModeEnableGuid:
switch name {
case "CustomMode":
return b8(v.Binary)
}
case MtcVendorGuid:
switch name {
case "MTC":
return u32(v.Binary)
}
case EfiSecureBootEnableDisableGuid:
switch name {
case "SecureBootEnable":
return b8(v.Binary)
}
case EfiImageSecurityDatabaseGuid:
switch name {
case "db", "dbr", "dbt", "dbx":
return esl(v.Binary)
}
case EdkiiVarErrorFlagGuid:
switch name {
case "VarErrorFlag":
return errorFlag(v.Binary)
}
case IScsiConfigGuid:
switch name {
case "InitialAttemptOrder":
return attemptOrder(v.Binary)
}
case Tcg2ConfigFormSetGuid:
switch name {
case "TCG2_CONFIGURATION", "TCG2_DEVICE_DETECTION":
return tpmVersion(v.Binary)
case "TCG2_VERSION":
return tcg2Version(v.Binary)
}
case EfiMemoryOverwriteRequestControlLockGuid:
switch name {
case "MemoryOverwriteRequestControlLock":
return morControlLock(v.Binary)
}
case EfiMemoryOverwriteControlDataGuid:
switch name {
case "MemoryOverwriteRequestControl":
return morControl(v.Binary)
}
case BmHardDriveBootVariableGuid:
switch name {
case "HDDP":
return devicePath(v.Binary)
}
case EfiCertDbGuid:
switch name {
case "certdb":
return certDB(v.Binary)
}
}
return nil, errUnexpectedData
}()
if hasFourDigits {
switch name[:n-4] {
case "Boot", "Driver", "SysPrep", "OsRecovery", "PlatformRecovery":
return boot
case "Key":
return keyboard
}
}
switch name {
case "BootOrder":
return bootOrder("Boot")
case "ConIn", "ConOut", "ErrOut":
return devicePath
case "DriverOrder":
return bootOrder("Driver")
case "KEK", "PK":
return esl
case "Lang", "PlatformLang":
return zn8
case "OsIndications":
return osIndications
case "Timeout":
return u16
}
case ShimLockGuid:
switch name {
case "MokList":
return esl
case "SbatLevel":
return z8
}
case EfiVendorKeysNvGuid:
switch name {
case "VendorKeysNv":
return b8
}
case EfiCustomModeEnableGuid:
switch name {
case "CustomMode":
return b8
}
case MtcVendorGuid:
switch name {
case "MTC":
return u32
}
case EfiSecureBootEnableDisableGuid:
switch name {
case "SecureBootEnable":
return b8
}
case EfiImageSecurityDatabaseGuid:
switch name {
case "db", "dbr", "dbt", "dbx":
return esl
}
case EdkiiVarErrorFlagGuid:
switch name {
case "VarErrorFlag":
return errorFlag
}
case IScsiConfigGuid:
switch name {
case "InitialAttemptOrder":
return attemptOrder
}
case Tcg2ConfigFormSetGuid:
switch name {
case "TCG2_CONFIGURATION", "TCG2_DEVICE_DETECTION":
return tpmVersion
case "TCG2_VERSION":
return tcg2Version
}
case EfiMemoryOverwriteRequestControlLockGuid:
switch name {
case "MemoryOverwriteRequestControlLock":
return morControlLock
}
case EfiMemoryOverwriteControlDataGuid:
switch name {
case "MemoryOverwriteRequestControl":
return morControl
}
case BmHardDriveBootVariableGuid:
switch name {
case "HDDP":
return devicePath
}
case EfiCertDbGuid:
switch name {
case "certdb":
return certDB
}
}
return dissector{
dissect: func([]byte) (any, error) { return nil, errUnexpectedData },
format: func(json.RawMessage) ([]byte, error) { return nil, errUnexpectedData },
}
}
// Dissect dissects an UEFI variable.
func Dissect(v *api.InstanceNVRAMVariable, guid string, name string) error {
dissected, err := getDissector(guid, name).dissect(v.Binary)
if err == nil {
v.Data = dissected
}
return nil
}
// Format formats an UEFI variable.
func Format(v *api.InstanceNVRAMVariable, guid string, name string) error {
raw, ok := v.Data.(json.RawMessage)
if !ok {
return fmt.Errorf("Unexpected type %T", v.Data)
}
formatted, err := getDissector(guid, name).format(raw)
if err == nil {
v.Binary = formatted
}
return err
}