Skip to main content

The security report

GNOME Settings has a shield in Privacy & Security. Clicking it opens a dialog that says "Protected" or "Security Checks Failed" and offers to copy a technical report. Everything it knows comes from fwupd's GetHostSecurityAttrs, and fwupd's Host Security ID is an index of firmware protections: Secure Boot, the SPI descriptor, Intel BootGuard, TPM reconstruction.

Those matter, but a desktop user is asking something closer to "is this machine set up the way a careful person would set it up". Is the disk encrypted? Is the kernel locked down, are modules signed, is the IOMMU on, are the usual hardening sysctls set? None of that appears in the dialog, because fwupd reports on the OS only where it has a plugin for it. lynis answers those questions for servers, as a shell script that prints a long text report.

This OS answers both sets of questions in one list.

Two providers, one shape​

losos-security, in src/losos-security/, runs the OS-level checks and serves them on the system bus as io.losos.Security1. It answers GetHostSecurityAttrs, the same method name fwupd uses, with the same a{sv} dictionary keys. Because the answers have fwupd's shape, the gnome-control-center patch only asks a second bus name and files the answers separately. It needs no second parser and no second widget. src/attr.rs pins the key set against the code that reads it, gnome-control-center's panels/privacy/firmware-security/cc-firmware-security-utils.c.

The attributes go into their own table, not into the HSI tables. fwupd defines the HSI levels for firmware, and filing "disk encryption" under HSI-1 would claim the two are equivalent when they are not.

The checks​

Each check reports a FwupdSecurityAttrResult and a level. Unlike fwupd's, it also reports the path it read and what it found there.

CheckReads
UEFI Secure Boot/sys/firmware/efi/efivars/SecureBoot-8be4df61-…, fifth byte
TPM 2.0 present/sys/class/tpm/tpm0/tpm_version_major
Measured boot/sys/kernel/security/tpm0/binary_bios_measurements
Disk encryption/sys/block/*/dm/uuid, prefix CRYPT-LUKS
Verified system image/sys/block/*/dm/uuid, prefix CRYPT-VERITY
Kernel lockdown/sys/kernel/security/lockdown, the bracketed mode
Module signature enforcement/sys/module/module/parameters/sig_enforce
IOMMU/sys/class/iommu
Kernel address exposurekernel.kptr_restrict ≥ 1
Kernel log accesskernel.dmesg_restrict ≥ 1
Unprivileged BPFkernel.unprivileged_bpf_disabled ≥ 1
BPF JIT hardeningnet.core.bpf_jit_harden ≥ 1

Two rules matter here, and a careless change can break either.

Every check names where it looked. If a verdict comes without its evidence, nobody can tell when it is wrong. The evidence goes in the Description field, so the panel shows it when the row is expanded.

Every check is rooted. No check opens an absolute path. They all go through Context::path, which prefixes a configurable root. That one function call makes the suite testable: tests/checks.rs builds a fixture directory of sysfs files and tests every check passing and failing, with no TPM and no root.

The overall level is the weakest link, the highest level at which every check passed. It is not a score or a percentage. A machine with nine passes and one level-1 failure has a level-0 problem, and an average would hide it. A level with no checks in it does not count as passed. all() over an empty iterator is true, so without that rule an untested level would report the best possible answer.

The panel change​

nixos/pkgs/patches/gnome-control-center/0002-firmware-security-report-os-level-checks.patch does three things:

  • Adds a third D-Bus proxy, to io.losos.Security1, beside the two fwupd ones, and files its reply into a separate table through the existing parser.
  • Counts replies to decide whether the page is available, instead of leaving it to one callback. The two providers can reply in either order, so neither can decide alone that there is nothing to show. The "unavailable" page appears only when both have failed. The fwupd failure path now counts itself, which upstream never needed. Without that, a machine with the OS provider and no fwupd would wait forever for a verdict. Upstream hides the page on a virtual machine, where a firmware index means nothing. If any provider answered, the page shows anyway, because the OS checks still apply there.
  • Lists the checks in the dialog. Upstream shows them only inside the copied technical report. That made sense while every check was about firmware the reader cannot change from here. This list includes a lockdown mode and four sysctls, and a reader who is told which one failed can fix it.

Running it​

losos-security # human-readable; exit status is the verdict
losos-security --json
losos-security --root ./fixture # every check against a fixture tree

The service is bus-activated, not enabled. The unit has no [Install] section. io.losos.Security1.service in dbus-1/system-services names SystemdService=, so dbus-daemon hands the start to systemd and the sandbox in losos-security.service applies. It runs as root, because two of the paths above are not world-readable. The sandbox then takes away everything else a root process could do. It has no capabilities, no writable filesystem and no network namespace, may open only AF_UNIX sockets, and may make only the @system-service system calls. A security checker that an attacker could exploit would make the machine less safe.