LXD VM instance EFI Variables edit CLI

Index LX067
Title LXD VM instance EFI Variables edit CLI
Subteam LXD
Status Implemented
Authors Aleksandr Mikhalitsyn
Stakeholders Thomas Parrott
Type Implementation
Created Feb 7, 2024
Release 5.21.0

Abstract

This feature will introduce a bunch of LXD CLI subcommands like lxc config uefi show, lxc config uefi edit, etc to allow EDKII NVRAM file manipulation and changing EFI variables values.

Rationale

It was requested by our colleagues from Foundations team as they want to have an convenient CLI interface to edit EFI variables mostly for EFI/SHIM/bootloader debugging.

Specification

Design

We want to use GitHub - awslabs/python-uefivars: Python package and helper tool to analyze, convert and modify UEFI variable stores as a internal low-level tool to manipulate with the EDKII NVRAM files. This package also provides an interface to convert an EDKII NVRAM file to JSON and back from JSON. It’s convenient from the implementation standpoint. JSON output looks like this:

$ uefivars -i edk2 -o json -I qemu.nvram 2>/dev/null
{
	"version": 2,
	"variables": [
    	{
        	"name": "Attempt 1",
        	"data": "000000000000000...WAS CUT BY ME...0000000000000",
        	"guid": "59324945-ec44-4c0d-b1cd-9db139df070c",
        	"attr": 3
    	},

We don’t want to expose this JSON format that python-uefivars uses directly, but instead we define an analogical format in our API and then on the LXD server side we will be able to convert from one format to another. Generally speaking, we can go with precisely the same structure, but, obviously, start with the version 1. The reason for that is we may want to switch the tool to something else in the future but remain compatible. We have seen (https://github.com/awslabs/python-uefivars/commit/1e464d77d81e9b74755bedc26b0f027650e9004e) that output format in this package was changed once.

Final structure can be like this:

{
    		"name:<UTF-8 encoded string>-guid:<UUID string>": {
        		"data": <HEX representation of the bytes array (hex.Encode in Golang or bytes.hex() in Python)>,
        		"attr": <decimal (?) represented attrs mask>,
           	(optional) "timestamp": <HEX representation>,
           	(optional) "digest": <HEX representation>,
    		},
		<..>
}

As you can see we go with the combination of <name>-<UUID> as a key in JSON map. That’s because the EFI variable name is not a unique and it’s perfectly fine to have a few EFI variables with the same name. What is unique is a pair (UUID, name). By doing that we just following a semantic that efivarfs has:

$ ls -la /sys/firmware/efi/efivars/
total 0
drwxr-xr-x 2 root root     0 Feb  8 09:04 .
drwxr-xr-x 5 root root     0 Feb  8 09:04 ..
-rw-r--r-- 1 root root    76 Feb  8 09:04 AbtStatus-a0b1889e-00eb-445b-8ca9-e91ce43c907d
-rw-r--r-- 1 root root    14 Feb  8 09:04 AmdAcpiVar-79941ecd-ed36-49d0-8124-e4c31ac75cd4

It’s worth mentioning that the entire JSON string will be encoded as UTF-8. EFI variable names are supposed to be represented as UCS-2, but as we can represent any UCS-2 character in UTF-8 it’s safe just to use UTF-8 everywhere. Internally, python-uefi vars expects us to provide file encoded as UTF-8 (https://github.com/awslabs/python-uefivars/blob/04a6e47d13b2975e4f11581559b210ac2aa5e8c5/pyuefivars/json.py#L36).

API changes

Types

type InstanceEFI struct {
	// EFI variables map
	// Hashmap key format is <efi-variable-name>-<UUID>
	// Example: AmdAcpiVar-79941ecd-ed36-49d0-8124-e4c31ac75cd4
	Variables map[string]*InstanceEFIVariable `json:"variables" yaml:"variables"`
}

type InstanceEFIVariable struct {
	// EFI variable data (HEX-encoded)
	Data string `json:"data" yaml:"data"`

	// EFI variable attributes
	Attr uint32 `json:"attr" yaml:"attr"`

	// EFI variable timestamp (HEX-encoded)
	Timestamp string `json:"timestamp" yaml:"timestamp"`

	// EFI variable digest (HEX-encoded)
	Digest string `json:"digest" yaml:"digest"`
}

Routes

  • GET instances/{name}/efi_vars to read all instance’s EFI variables
  • PUT instances/{name}/efi_vars to update instance’s EFI variables

Both routes are only valid for VM instance type.

CLI changes

Introduce new LXD CLI subcommands:

  • lxc config uefi show
  • lxc config uefi edit
  • lxc config uefi set key=val
  • lxc config uefi unset key

Database changes

No database changes.

Could we use the same naming scheme as the /sys/firmware/efi/efivars/ uses, e.g. <name>-<guid> for the variables map key?

I’m not sure we need this field. We don’t normally include version fields in each API struct type, but instead use the API extensions concept to indicate if future additional fields/changes have occurred.

Unless this indicates any specifically to UEFI I think we should drop it.

Sure, we can. I wanted to have something like a0b1889e-00eb-445b-8ca9-e91ce43c907d-AbtStatus (reverse order). But it’s not important. Let’s just follow efivarfs scheme.

1 Like

Ah, ok. So, if we change this structure in the future, we will be able to easily detect from the LXD server side that client-side is old and follow proper fallback path, right? And, inversely, can we from the client side get all the features list that supported by server?

1 Like

Yeah. Generally speaking new fields can be indicated to the client via an API extension, and field name changes/removals are strongly frowned upon as its an API breakage. But if absolutely necessary then would have to come with an API extension as well so that the client can adapt to older and newer servers.

1 Like

Makes sense! Thanks!

I have fixed the spec.

1 Like

@tomp Do we want to allow lxc config uefi ... not only for the virtual machine instances, but also for a snapshots, right? I have checked that for VM instance shapshots we also maintain a copy of the NVRAM file, not only a disk image, so we can support this too. Do you think that it can be useful?

Good question. But snapshots are read only so cannot be modified.

1 Like

Got it. So, I just need to check that we are not dealing with the snapshot, otherwise just return an error.

Offtop. We also have a concept of an instance property, and as far as I understand they can be modified even for instance snapshots (https://github.com/canonical/lxd/commit/a6d6d4b7e7aaa4c502b9483b87418375a6f62c24#diff-b1f9f347147b2b914f8b6453989464f313f0000f0a9b32c7aaf5a554593426b7R631).

Ah, no, we have this https://github.com/canonical/lxd/commit/5ca04abd216d7bcacfeb948582445f714140b232

Yes snapshot expiry can be changed after the snapshot is taken, but thats all AFAIK.

To conclude: we had a small private conversation with Tom. Final outcome from it is that we don’t want to allow editing EFI variables for snapshots.

1 Like