mirror of
https://github.com/lxc/incus
synced 2026-08-02 05:26:46 +00:00
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:
parent
5eaad262c2
commit
65dfe674f3
@ -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
|
||||
},
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
})
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user