diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index aa29989..d714f41 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -1,5 +1,6 @@ -# The checks of the Makefile; the QEMU boots, which need KVM and apt packages, and -# the unit tests on macOS and Windows in jobs of their own. +# The checks of the Makefile in a job per kind: lint, which reads the source of every system on +# one host, and the unit tests and the QEMU tests, which run make -k for each system in +# a matrix entry named after its operating system and architecture. name: ci on: @@ -17,8 +18,8 @@ concurrency: cancel-in-progress: true jobs: - # The image of test-e2e-qemu too: one image, one migration. - lint-and-test: + # The Ubuntu release of the other Linux jobs too, ubuntu-26.04-arm on arm64: one release, one migration. + lint: runs-on: ubuntu-26.04 timeout-minutes: 60 steps: @@ -26,48 +27,46 @@ jobs: with: persist-credentials: false - # gcc's 32-bit libraries, which test-i686 links with. - - run: | - sudo apt-get update - sudo apt-get install --yes gcc-multilib - - run: make setup # After setup: the key covers installed toolchains. - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - # Every check but the boots. - - run: make -k lint test test-i686 + - run: make -k lint - # The unit tests of the macOS build on both of its targets; lint-and-test lints them. - test-macos: + # The unit tests of each system on a runner of that system: test runs those of the host's + # build, and test-i686 those of the 32-bit glibc build on x86_64 Linux. The Windows build's + # firmware calls run against fakes. + test: + name: test (${{ matrix.system }}) strategy: fail-fast: false matrix: - runner: [macos-latest, macos-15-intel] + include: + - system: linux-x86_64 + runner: ubuntu-26.04 + check: test + - system: linux-i686 + runner: ubuntu-26.04 + check: test-i686 + - system: linux-aarch64 + runner: ubuntu-26.04-arm + check: test + - system: macos-aarch64 + runner: macos-26 + check: test + - system: macos-x86_64 + runner: macos-15-intel + check: test + - system: windows-x86_64 + runner: windows-2025 + check: test + - system: windows-aarch64 + runner: windows-11-arm + check: test runs-on: ${{ matrix.runner }} timeout-minutes: 60 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - # GNU make 4 as gmake: Apple's make 3.81 ignores the Makefile's .SHELLFLAGS (3.82). - - run: brew install make - - - run: gmake setup - - # After setup: the key covers installed toolchains. - - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - - - run: gmake -k test - - # The unit tests of the Windows build on x86_64, whose firmware calls run against - # fakes; lint-and-test lints both of its targets. - test-windows: - runs-on: windows-2025 - timeout-minutes: 60 - # Git for Windows' bash, which the Makefile's recipes and scripts need. + # Git for Windows' bash on Windows, which the Makefile's recipes and scripts need. defaults: run: shell: bash @@ -76,19 +75,56 @@ jobs: with: persist-credentials: false + # gcc's 32-bit libraries, which test-i686 links with. + - if: matrix.check == 'test-i686' + run: | + sudo apt-get update + sudo apt-get install --yes gcc-multilib + + # GNU make 4, ahead of Apple's make 3.81 on PATH, which ignores the Makefile's + # .SHELLFLAGS (3.82). + - if: runner.os == 'macOS' + run: | + brew install make + echo "$(brew --prefix make)/libexec/gnubin" >>"$GITHUB_PATH" + # GNU make, which the image lacks. - - run: choco install make --yes --no-progress + - if: runner.os == 'Windows' + run: choco install make --yes --no-progress - run: make setup - # After setup: the key covers installed toolchains. + # After setup: the key covers installed toolchains. The system tells apart the entries + # of one runner image. - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + with: + key: ${{ matrix.system }} - - run: make -k test + - run: make -k "$CHECK" + env: + CHECK: ${{ matrix.check }} + # Each system that the QEMU tests boot, Linux or the UEFI Shell, on each architecture. # QEMU 9.0 and newer fault unaligned loads from Device memory as arm64 does, which the # aarch64 guests check; Ubuntu 24.04 has QEMU 8.2. test-e2e-qemu: + name: test-e2e-qemu (${{ matrix.system }}) + strategy: + fail-fast: false + matrix: + include: + - system: linux-x86_64 + check: test-e2e-qemu-x86_64 + - system: linux-i686 + check: test-e2e-qemu-i686 + - system: linux-aarch64 + check: test-e2e-qemu-aarch64 + - system: uefi-shell-x86_64 + check: test-e2e-qemu-shell-x86_64 + - system: uefi-shell-i686 + check: test-e2e-qemu-shell-i686 + - system: uefi-shell-aarch64 + check: test-e2e-qemu-shell-aarch64 runs-on: ubuntu-26.04 timeout-minutes: 120 steps: @@ -109,27 +145,31 @@ jobs: sudo udevadm trigger --name-match=kvm # No rust-cache: its cleanup takes run.sh's initramfs/tests for a cargo - # profile and logs errors; the builds take about a minute without it. + # profile and logs errors; the builds take about 2.5 minutes without it. - run: make setup - # The guests' packages, each architecture's DriverSample.efi and Secure Boot firmware, - # keyed also on the apt packages, the pinned iasl and the runner image (this file) that - # build them. Saved only on success, as run.sh reuses DriverSample.efi and the firmware - # unchecked. + # The system's guest packages, DriverSample.efi and Secure Boot firmware, keyed also on + # the apt packages, the pinned iasl and the runner image (this file) that build them. Per + # system, not per architecture: the Linux scenarios also boot the guests of + # guest-packages--unfixed*, which a key that the UEFI Shell entry saved first would + # lack until the key changes. Saved only on success, as run.sh reuses DriverSample.efi and + # the firmware unchecked. - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | target/qemu-test/packages target/qemu-test/*/driver-sample target/qemu-test/*/secboot-firmware - key: qemu-inputs-${{ hashFiles('tests/qemu/guest-packages-*', 'tests/qemu/build-driver-sample.sh', 'tests/qemu/build-secboot-firmware.sh', 'tests/qemu/host-packages', 'tests/qemu/ubuntu-packages', '.github/workflows/checks.yml') }} + key: qemu-inputs-${{ matrix.system }}-${{ hashFiles('tests/qemu/guest-packages-*', 'tests/qemu/build-driver-sample.sh', 'tests/qemu/build-secboot-firmware.sh', 'tests/qemu/host-packages', 'tests/qemu/ubuntu-packages', '.github/workflows/checks.yml') }} - - run: make -k test-e2e-qemu + - run: make -k "$CHECK" + env: + CHECK: ${{ matrix.check }} - if: failure() uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: - name: qemu-test-logs + name: qemu-test-logs-${{ matrix.system }} path: | target/qemu-test/*/*/*/*.log target/qemu-test/*/*/executables diff --git a/Cargo.lock b/Cargo.lock index 9593168..bb0d7f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1295,6 +1295,7 @@ dependencies = [ "bitflags 2.4.1", "clap", "cms", + "crc32fast", "der", "env_logger", "fbthrift-git", diff --git a/Cargo.toml b/Cargo.toml index 706b9f9..410a13f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,13 @@ der = { version = "0.7", features = ["alloc", "pem", "std"] } rand_core = { version = "0.6", default-features = false } # The RNG of RSA blinding on every target, seeded once from the platform's source. rand_chacha = { version = "0.3", default-features = false } +# BootOptionCrc, the CRC-32 of the Boot#### variable that a Key#### hot key starts, and the +# CRC32s of the GPT on which the Linux and Windows builds find the ESP. +crc32fast = "1.5.2" + +# The Linux and Windows builds stage a set for the next boot under a random nonce. +[target.'cfg(any(target_os = "linux", target_os = "windows"))'.dependencies] +rand = { version = "0.8", features = ["small_rng"] } # Only the Linux build uses these: efivarfs, the HiiDB in sysfs, iLO through # ilorest_chif.so, and the CLI's logging. @@ -52,7 +59,6 @@ libc = "0.2" nix = { version = "0.27", features = ["fs", "ioctl", "mount"] } libloading = "0.8" httparse = "1.8" -rand = { version = "0.8", features = ["small_rng"] } proc-mounts = "0.3" env_logger = "0.10" diff --git a/Makefile b/Makefile index c1d8704..60a4f7d 100644 --- a/Makefile +++ b/Makefile @@ -10,9 +10,9 @@ # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# The checks of uefisettings. They need GNU make and bash, rustup while -# RUST_TOOLCHAIN is set, as it is by default, for test-i686 an x86 host with -# gcc's 32-bit libraries, which README.md names, and for test-e2e-qemu an +# The checks of uefisettings. They need GNU make 3.82 or newer and bash, rustup +# while RUST_TOOLCHAIN is set, as it is by default, for test-i686 an x86 host +# with gcc's 32-bit libraries, which README.md names, and for test-e2e-qemu an # x86_64 host with /dev/kvm and what tests/qemu/README.md lists: # make every check of CHECKS, stopping at the first that fails # make -k every check of CHECKS, even after one fails @@ -39,6 +39,10 @@ SHELL := bash .SHELLFLAGS := -euo pipefail -c +# GNU make 3.81, macOS's make, ignores .SHELLFLAGS, which came with .ONESHELL in 3.82. +ifeq ($(filter oneshell,$(.FEATURES)),) +$(error GNU make 3.82 or newer runs the checks, e.g. Homebrew's gmake on macOS) +endif # The Rust release that the checks run on: newer releases bring lints that the # code does not satisfy yet. make setup installs it; while RUST_TOOLCHAIN is set, @@ -65,15 +69,16 @@ unexport CARGO_ENCODED_RUSTFLAGS TARGETS := x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu,i686-unknown-linux-gnu,x86_64-unknown-linux-musl,x86_64-unknown-uefi,i686-unknown-linux-musl,i686-unknown-uefi,aarch64-unknown-linux-musl,aarch64-unknown-uefi,x86_64-apple-darwin,aarch64-apple-darwin,x86_64-pc-windows-msvc,aarch64-pc-windows-msvc comma := , -# CI runs lint test test-i686 and test-e2e-qemu, and test on macOS and Windows: a -# new check joins LINTS or QEMU_TESTS or CI runs it too. -LINTS := fmt clippy-x86_64 clippy-aarch64 clippy-i686 clippy-uefi clippy-macos clippy-windows doc +# CI runs LINTS as one check, lint: a new check joins LINTS, or ci-jobs fails until the workflow names it. +LINTS := fmt clippy-x86_64 clippy-aarch64 clippy-i686 clippy-uefi clippy-macos clippy-windows doc ci-jobs QEMU_LINUX_TESTS := test-e2e-qemu-x86_64 test-e2e-qemu-i686 test-e2e-qemu-aarch64 QEMU_SHELL_TESTS := test-e2e-qemu-shell-x86_64 test-e2e-qemu-shell-i686 test-e2e-qemu-shell-aarch64 QEMU_TESTS := $(QEMU_LINUX_TESTS) $(QEMU_SHELL_TESTS) # The scenarios of tests/qemu/scenarios.tsv: those named shell-* boot the UEFI Shell. SCENARIOS := $(shell awk -F '\t' '$$1 ~ /^[a-z]/ { print $$1 }' tests/qemu/scenarios.tsv) CHECKS := $(LINTS) test test-i686 test-e2e-qemu +# The checks that CI runs as make -k , each in a job or job entry of its own. +CI_CHECKS := lint $(filter-out $(LINTS) test-e2e-qemu,$(CHECKS)) $(QEMU_TESTS) .PHONY: check lint help setup toolchain $(CHECKS) $(QEMU_TESTS) @@ -135,6 +140,11 @@ clippy-windows: toolchain cargo clippy --locked -p uefisettings --lib --bins --all-targets --target x86_64-pc-windows-msvc \ --target aarch64-pc-windows-msvc -- -D warnings +# The workflow names every check of CI_CHECKS and no other, as scripts/check-ci-jobs.sh reads +# it; which systems run a check is the workflow's alone. Needs no toolchain. +ci-jobs: + scripts/check-ci-jobs.sh .github/workflows/checks.yml $(CI_CHECKS) + test: toolchain scripts/assert-tests-ran.sh cargo test --locked diff --git a/README.md b/README.md index d2d5ed2..cf4a507 100644 --- a/README.md +++ b/README.md @@ -113,8 +113,16 @@ write no variable of a HiiDB with one, nor do `hii list-questions` and `hii show its Config Keyword Handler takes, counts as one outside any FormSet, also for the password questions of FormSets of its VarStoreId. IFR after an op that does not fit its form package has no op boundary, so they read and write no variable of such a HiiDB, nor do -`hii list-questions` and `hii show-ifr` but with `--no-efivars`. A password question whose VarStore -uefisettings cannot find withholds no bytes, which they warn of. A password question that edk2 may +`hii list-questions` and `hii show-ifr` but with `--no-efivars`. A password question of a VarStoreId +that no VarStore of its form package that decodes declares names no VarStore (UEFI 2.11 +§33.3.8.2.2 asks for "a previously declared variable store"), and edk2's Forms Browser finds none +for it but a VarStore op of its FormSet, outside an unknown opcode's scope, that does not decode, +whose fields it reads past the op's end, so they read and write no variable of a HiiDB with one, +nor do `hii list-questions` and +`hii show-ifr` but with `--no-efivars`; one whose FormSet declares no VarStore of its VarStoreId, +which another VarStore of its form package declares, keeps its password at its offset in the first +of those, which edk2's Config Keyword Handler takes, where `get` withholds it, and they warn of it. +A password question that edk2 may route to a VARSTORE_NAME_VALUE, whose values its driver exposes, as UEFI 2.11 §33.2.5.6 says, through EFI_HII_CONFIG_ACCESS_PROTOCOL, in storage of its own choosing, may keep its password in any variable, so they read and write no variable of a HiiDB with one, as for IFR without an op boundary, @@ -132,18 +140,30 @@ writes no variable of a HiiDB with such a password question or with IFR that it ```plain Commands: - get Show the attributes and the decoded value of a global variable of UEFI 2.11 section - 3.3, but its load options, hot keys and device paths, or of a signature database, or - that it is absent - help Print this message or the help of the given subcommand(s) - info Show the attributes and the size of an EFI variable, never its data - list List the name and vendor GUID of every EFI variable, never its data - set Set a global variable of UEFI 2.11 section 3.3 that the OS may write: Timeout, - BootOrder, DriverOrder, SysPrepOrder, BootNext, PlatformLang or OsIndications - storage Show the storage space of the EFI variables of some attributes, and how much of it - is left - write-signed Write an EFI variable from a signed payload, or from new data that a key signs; the - firmware verifies the signature + create-key Create a Key#### that does not exist: a hot key that starts a Boot#### + create-load-option Create a Boot####, Driver#### or SysPrep#### that does not exist, from device + paths as text or files of their bytes + get Show the attributes and the decoded value of a global variable of UEFI 2.11 + section 3.3, its load options, hot keys and device paths as text among them, + or of a signature database, or that it is absent + help Print this message or the help of the given subcommand(s) + info Show the attributes and the size of an EFI variable, and the + EFI_VARIABLE_AUTHENTICATION_3 metadata of one with + enhanced-authenticated-access, never its data + list List the name and vendor GUID of every EFI variable, never its data + set Set a global variable of UEFI 2.11 section 3.3 that the OS may write: Timeout, + BootOrder, DriverOrder, SysPrepOrder, BootNext, PlatformLang or OsIndications; + or, in the UEFI Shell, AuditMode and DeployedMode with + --i-am-fine-to-brick-this-system + set-attributes Set and clear the flags and set the category of a Boot####, Driver#### or + SysPrep####, keeping its other bits and bytes, and the Key#### of a Boot#### + following it + set-device-path Set ConIn, ConOut or ErrOut to a device path, as text or a file of its bytes, + keeping its attributes + storage Show the storage space of the EFI variables of some attributes, and how much + of it is left + write-signed Write an EFI variable from a signed payload, or from new data that a key + signs; the firmware verifies the signature ``` `capsule`: @@ -180,8 +200,9 @@ Commands: and the `hii` commands but `extract-db` and `dump-efivars` in the UEFI Shell, before an OS boots; it also reaches boot-service-only variables and the Time Services, which date and time questions of EFI Date/Time Storage read and write, and has no `-f` or `--efivars`. It reads the HiiDB -through the HII Database Protocol, and accepts `--hii-source` and `--efivar-memory` only to ignore -them. +through the HII Database Protocol, and refuses `--hii-source` and `--efivar-memory` as unexpected +arguments. Its `apply-staged` applies a set that `set` staged from Linux or Windows for the next +boot: see the [appendix](#appendix). ```sh rustup target add x86_64-unknown-uefi # or i686-unknown-uefi, aarch64-unknown-uefi @@ -192,8 +213,8 @@ cargo build --release --target x86_64-unknown-uefi FS0:\> uefisettings.efi hii get --json "Activate this check box" ``` -Exit statuses, setup passwords, `variable`, capsules and Secure Boot: see the -[appendix](#appendix). +Exit statuses, setup passwords, `variable`, capsules, Secure Boot and sets staged for the next +boot: see the [appendix](#appendix). --- @@ -207,12 +228,13 @@ be assigned to the process token; running elevated alone does not guarantee it. Pass `--no-efivars` to read no variables. No driver is shipped with this repository, and none has been tested here. Without a driver, `-f` still reads a HiiDB file, and, when such a driver is installed, `hii extract-db` writes its HiiDB to a file. `--hii-source` and -`--efivar-memory` are accepted and ignored. +`--efivar-memory` are refused as unexpected arguments. `variable list` and `variable storage` fail there: the firmware environment calls reach a variable by its name only, and have no QueryVariableInfo(). So does `capsule deliver`: those calls reach EFI variables only. `capsule config-data` and `capsule result-table` fail too: `uefisettings` reads the configuration tables of the EFI System Table only in the UEFI Shell, and on -Linux on x86, x86_64 and aarch64. +Linux on x86, x86_64 and aarch64. As on Linux, `set` stages a question whose value only its driver +holds for the next boot, through the ESP's volume: see the [appendix](#appendix). --- @@ -223,8 +245,8 @@ and writes nothing: `hii get`, `hii list-questions` and `hii show-ifr` read ques an efivars dump that `hii dump-efivars` wrote on Linux (`--efivars DIR`, with its HiiDB unless `-f` names one) or from none (`--no-efivars`, with `-f`), and `hii list-strings` reads the HiiDB file of `-f`. It has no `get`, `set`, `identify`, `ilo`, `variable`, `capsule`, `hii set`, -`hii extract-db` or `hii dump-efivars`; it accepts `--hii-source` and `--efivar-memory` and ignores -them. Dumps: see the [appendix](#appendix). +`hii extract-db` or `hii dump-efivars`; it refuses `--hii-source` and `--efivar-memory` as +unexpected arguments. Dumps: see the [appendix](#appendix). --- @@ -251,6 +273,7 @@ otherwise. | Operation | Commands | Build | Consequence | |---|---|---|---| | A write in a HiiDB that declares a setup password question | `set`, `hii set`, `hii keyword set` | UEFI Shell | No password is asked or checked, as the UEFI Shell runs no setup browser | +| A set staged for the next boot in a HiiDB that declares a setup password question | `set`, `hii set`, which record the flag for `apply-staged` | Linux, Windows | The next boot applies it before the OS, where no setup browser asks for or checks the password | | A write of a question that `EFI_IFR_FLAG_READ_ONLY` makes read-only | `set`, `hii set` | Linux, UEFI Shell, Windows | A value changes that the firmware declares read-only; the Keyword Handler refuses it as "a violation of system policy" (UEFI 2.11 §35.3.2) | | A signed write of a Secure Boot policy variable (PK, KEK, OsRecoveryOrder, OsRecovery####, any variable of EFI_IMAGE_SECURITY_DATABASE_GUID) while SetupMode is 1, cannot be read or holds other than a UINT8 of 0 or 1 | `variable write-signed` | Linux, UEFI Shell, Windows | The firmware writes it without checking the signature (UEFI 2.11 §3.3, §32.3) | | A signed write of a device signature database (any variable of EFI_DEVICE_SECURITY_DATABASE_GUID) while SetupMode is 1, cannot be read or holds other than a UINT8 of 0 or 1 | `variable write-signed` | Linux, UEFI Shell, Windows | The firmware may write it without checking the signature (UEFI 2.11 §32.6.3) | @@ -262,6 +285,16 @@ otherwise. which UEFI 2.11 has check it against a schema and answer per capsule (§8.5.6.1.3), unlike SetVariable(), which stores bytes with "no read, write, or delete security protection" (§8.2.3). +The boot manager's writes take no flag either: `variable set` of its variables and `variable +set-attributes`, `create-load-option`, `set-device-path` and `create-key` write variables of UEFI +2.11 Table 3.1 that the OS writes, and another write or a deletion undoes each (§8.2.3); each +`warning:` line they print names what the boot manager may ignore, not a harm to the machine. + +`get` of a question whose value only its driver keeps takes no flag either: it restores any store +that its RETRIEVE Callback() changed, which undoes only the driver's own change, and its +ExtractConfig() reads of the FormSet's Buffer and Name/Value stores reach the driver without the +flag too (see the UEFI Shell details). + --- ## Changing UEFI settings with automation @@ -286,12 +319,12 @@ But this tool is an unified opensource approach to manipulate UEFI settings on a * `systab`, on x86, x86_64 and aarch64: read the HII database export that the firmware lists as the EFI configuration table of `EFI_HII_DATABASE_PROTOCOL_GUID` (UEFI 2.11 §33.2.11.1), as EDK2 does where `PcdHiiOsRuntimeSupport` is TRUE, from `/dev/mem`, as `capsule config-data` reads its table: the package lists from the buffer's start up to a package list header of zeros, which EDK2 leaves after them, refusing fewer zero bytes than a header before other bytes, which EDK2's export into its old buffer can leave, or up to the end of the buffer's EFI runtime region, each whole inside that region and ending with an EFI_HII_PACKAGE_END package exactly at its PackageLength (UEFI 2.11 §33.3.1.1), which the other ways do not require, at most 16 MiB in all. It takes the table's address as physical first, then as virtual. It does not support Xen: on aarch64, where `/chosen` has no `linux,uefi-system-table` and `/hypervisor/uefi` holds Xen's `xen,uefi-system-table`, it fails. On x86 it reads only where Linux exports /sys/firmware/efi/runtime-map (CONFIG_EFI_RUNTIME_MAP, which CONFIG_EFI selects with CONFIG_KEXEC_CORE: Linux v6.11 arch/x86/Kconfig:2001, :2054), which bounds its reads. The configuration that the firmware exports with HII ExportConfig() and lists as the EFI configuration table of `EFI_HII_CONFIG_ROUTING_PROTOCOL_GUID` (UEFI 2.11 §33.2.11.1) is read the same way: its text up to its first NUL CHAR16, inside the buffer's EFI runtime region. - `auto` moves on while a way finds its source absent (no vendor ACPI table file, no `HiiDB` variable, no EFI System Table that Linux locates, as on a BIOS boot or an aarch64 boot without UEFI, or no such configuration table) or fails otherwise, such as a table that fails its checks or a missing memory map, and stops at a refusal: kernel lockdown, the kernel refusing `/dev/mem` or `/proc/kcore` (no `CAP_SYS_RAWIO`, `CONFIG_STRICT_DEVMEM`), a file that the user may not read, or an unsafe operation without `--i-am-fine-to-brick-this-system`. Its error names each way that it tried, in order, with why, absent apart from refused; with `RUST_LOG=warn`, `uefisettings` logs each way that failed before the one that gave the HiiDB, and with `RUST_LOG=debug` also that way. A named way is the only one tried. `identify` checks the same ways for the HII backend: it finds none where each way finds its source absent, and assumes it, with a warning that `RUST_LOG=warn` shows, where a way's check fails or is refused, such as under kernel lockdown or without `CAP_SYS_RAWIO`. `--hii-source` and `--efivar-memory` may also follow the command; the UEFI Shell, Windows and macOS builds accept both and ignore them. + `auto` moves on while a way finds its source absent (no vendor ACPI table file, no `HiiDB` variable, no EFI System Table that Linux locates, as on a BIOS boot or an aarch64 boot without UEFI, or no such configuration table) or fails otherwise, such as a table that fails its checks or a missing memory map, and stops at a refusal: kernel lockdown, the kernel refusing `/dev/mem` or `/proc/kcore` (no `CAP_SYS_RAWIO`, `CONFIG_STRICT_DEVMEM`), a file that the user may not read, or an unsafe operation without `--i-am-fine-to-brick-this-system`. Its error names each way that it tried, in order, with why, absent apart from refused; with `RUST_LOG=warn`, `uefisettings` logs each way that failed before the one that gave the HiiDB, and with `RUST_LOG=debug` also that way. A named way is the only one tried. `identify` checks the same ways for the HII backend: it finds none where each way finds its source absent, and assumes it, with a warning that `RUST_LOG=warn` shows, where a way's check fails or is refused, such as under kernel lockdown or without `CAP_SYS_RAWIO`. `--hii-source` and `--efivar-memory` may also follow the command; the UEFI Shell, Windows and macOS builds refuse both as unexpected arguments. Linux refuses either where it reads no HiiDB of this machine: in `show-translations`, `variable`, `capsule` and `ilo`, in `get` and `set` where they take the Ilo backend, and where `-f` or the efivars dump of `--efivars` gives the HiiDB; and it refuses `--efivar-memory` in `identify`, whose efivar method only checks that the `HiiDB` variable exists, and with `--hii-source acpi` or `systab`, which never read that memory. * Library (`hii`) in Rust partially implements the UEFI Hii specification. * It parses the HiiDB op-codes into a DOM tree like representation which can be read by machines and humans. * It can ask questions about UEFI settings from HiiDB and get their answers. * The texts of a HiiDB and of EFI variables, such as prompts, helps and option texts, can hold characters that a terminal takes as commands, as a reordering of the text after them or as line breaks. The text output shows each control character (Unicode Cc), Bidi_Control character, U+2028 and U+2029 escaped as Rust's `char::escape_debug` spells it, such as `\u{1b}`, `\r` and `\u{202e}`, and `\` as `\\`, so that no text shows as another's escape; `hii show-ifr` shows a text's own line break as `\n` inside its line. A console that shows a character as another, such as edk2's VT100 terminal showing U+2510 as `\`, can still make a text look like an escape. `--json` keeps each text exact, and spells U+007F to U+009F, Bidi_Control characters, U+2028 and U+2029 as JSON `\u` escapes, which JSON parsers read back as those characters. `set` and `hii set` match VALUE and `--item` against the raw texts only, whether a prompt, a QuestionId or a keyword selects the question: a shown text that holds an escape matches only an option whose raw text is that text. The UEFI Shell build also escapes each character that `EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.TestString()` of the console it prints to refuses, as `\u{10d}` in text and as a JSON escape in a JSON string, as edk2's VT100 and PC-ANSI terminals would write such a character's low byte, such as CR for U+010D; it fails rather than print where that call fails. Every build escapes U+FFF0 and U+FFF1, which edk2's GraphicsConsole takes as a lasting change of the width of the characters after them, as `\u{fff0}` and `\u{fff1}` in text and as JSON escapes. -* `get`, `set` and `hii list-questions` evaluate the `suppressif`, `grayoutif` and `disableif` conditions of questions as EDK2's setup browser does, and select only questions that none of them suppresses, grays out or disables, unless `--include` names that state (`grayed-out`, `suppressed`, `disabled`); an answer ends with the state of a question that is not selectable. Where a condition cannot be evaluated from Linux, `set` refuses the question, while `get` and `hii list-questions` show it with the states it may be in and why. A one-of option in a `suppressif` counts as suppressed, and a question that a `locked` (`EFI_IFR_LOCKED`) locks as grayed out. UEFI 2.11 §33.3.8.3.37 addresses LOCKED to Forms Editors; uefisettings follows edk2-stable202608 SetupBrowserDxe, which grays out a locked statement and every statement of a locked form (Presentation.c InitializeDisplayStatement, IfrParse.c ParseOpCodes). +* `get`, `set` and `hii list-questions` evaluate the `suppressif`, `grayoutif` and `disableif` conditions of questions as EDK2's setup browser does, and select only questions that none of them suppresses, grays out or disables, unless `--include` names that state (`grayed-out`, `suppressed`, `disabled`); an answer ends with the state of a question that is not selectable. A form under a `disableif` outside forms whose condition is true holds no question, as UEFI 2.11 §33.3.8.3.14 has a Forms Processor not process it, evaluated once at its initialization (§33.2.5.2.1), and edk2-stable202608 SetupBrowserDxe does not parse it: no `--include` selects its questions; only `--keyword` finds them, as edk2's keyword handler does, and a condition that refers to one finds no such question, which edk2 evaluates as UNDEFINED. Where the `disableif`'s condition relies on more than constants (§33.3.8.3.14), or a true or non-constant `suppressif` or `grayoutif`, or another op whose expression edk2 evaluates there, such as a `value`, comes before the form in its scope with no `disableif` ending between them, which edk2 takes for the `disableif`'s, the questions of the form may be disabled. Where a condition cannot be evaluated from Linux, `set` refuses the question, while `get` and `hii list-questions` show it with the states it may be in and why. A one-of option in a `suppressif` counts as suppressed, and a question that a `locked` (`EFI_IFR_LOCKED`) locks as grayed out. UEFI 2.11 §33.3.8.3.37 addresses LOCKED to Forms Editors; uefisettings follows edk2-stable202608 SetupBrowserDxe, which grays out a locked statement and every statement of a locked form (Presentation.c InitializeDisplayStatement, IfrParse.c ParseOpCodes). * `set` refuses a question that `EFI_IFR_FLAG_READ_ONLY` makes read-only (UEFI 2.11 §33.3.8.2.2), as EDK2's keyword handler does, unless `--i-am-fine-to-brick-this-system` is given (see [Unsafe operations](#unsafe-operations)), and always one that an `EFI_IFR_VALUE` makes read-only (§33.3.8.3.88), before it writes any question; the refusal names the states to include to select a question of the same text that `set` writes, if any: a writable one, and, with that flag, one that `EFI_IFR_FLAG_READ_ONLY` makes read-only. Of questions with the same text, `get` and `set` pick a writable one first among matches equally certain to be selected. `get`, `hii get` and `hii list-questions` show read-only questions. * Where efivarfs lacks the variable of a question, as the firmware has not created it or gives it no runtime access, `get`, `hii list-questions` and `hii show-ifr` take its value from the current configuration that edk2 firmware exports with `ExportConfig()` at ReadyToBoot when built with `PcdHiiOsRuntimeSupport`, its default (UEFI 2.11 §33.2.11.1); so do the questions of name/value storage, which no EFI variable holds. uefisettings reads that snapshot on x86, x86_64 and arm64 from `/dev/mem`, as it reads the HiiDB that the configuration table lists. A value from it ends with ` `, as does the option of a ONE_OF that such a value in the EFI_IFR_VALUE of an option of Type OTHER decides, and so does the state that a condition decides from such a value, even selectable (` `); `get` refuses a question that such a state excludes, naming the snapshot. `set` never reads the snapshot: it refuses those questions as without it. No snapshot is read with `--efivars` or `--no-efivars`, where the firmware lists none, or where `/dev/mem` cannot be read (without root, or under a kernel lockdown). The bytes of PASSWORD questions are dropped from it, so a question over them shows withheld, and a PASSWORD question whose VarStore is unknown, or IFR that uefisettings ignores, which may hold one, keeps the whole snapshot off, as uefisettings could not withhold the bytes of that password. * Each question that `get`, `set` and `hii list-questions` print lists the Flags of its question header that are set (UEFI 2.11 §33.3.8.2.2), such as `READ_ONLY` and `RESET_REQUIRED`, in `flags`. A question of a FormSet whose ClassGuids hold `EFI_HII_REST_STYLE_FORMSET_GUID` lists `REST_STYLE`, which its Flags can then omit. @@ -305,7 +338,7 @@ But this tool is an unified opensource approach to manipulate UEFI settings on a * A date or time question whose UEFI Storage is the system time or the wake-up time, its EFI Date/Time Storage (UEFI 2.11 §33.2.5.6), keeps its value there, in addition to its VarStore, if any. In the UEFI Shell, `get` shows the fields of GetTime()'s or GetWakeupTime()'s time, or the status of the service that fails, and `set` reads that time first, changes the question's fields, keeps the others, the TimeZone and Daylight too, and writes it back with SetTime() or SetWakeupTime(TRUE, …), then writes the question's VarStore, if any; where that write fails, the error names the time written. `set` of a wake-up time thus arms the alarm, which "causes the system to wake up or power on at the set time" (UEFI 2.11 §8.3.4), as EDK2's setup browser does (edk2-stable202608 SetupBrowserDxe/Setup.c:2051). Before any write, `set` refuses a time outside the ranges of UEFI 2.11 §8.3.1, such as a year before 1900. A GET or SET of a date or time without a VarStore, which conditions and WRITE expressions may hold, reads or writes a field of GetTime()'s time, as EDK2's setup browser does. On Linux, Windows and macOS, which reach no Time Services, `get` shows `` (GetWakeupTime() for a wake-up time), also where the ExportConfig snapshot holds the bytes of its VarStore, as that snapshot holds no EFI_TIME, and `set` refuses the question, with every other question that it selects, before any write. * An ordered-list question (UEFI 2.11 §33.2.5.4.8, §33.3.8.3.53) holds its items in up to MaxContainers containers, each as wide as the Type of its first option, as EDK2's setup browser sizes them (edk2-stable202608 SetupBrowserDxe/IfrParse.c), and a container of 0 ends them. `get` and `hii list-questions` show the texts of the options of its items in order, joined by `, `, each with the state of its option, such as `USB