Development documentation

This is the in-progress lota-next branch. For the released version, see the stable documentation.

LOTA player install (lota-install)

lota-install is the player-side path onto a LOTA-attested host: one guided, reboot-resumable command instead of the operator checklist in production-bringup/index. It drives the same hard requirements the agent enforces at startup, but it explains every change before making it, asks for consent, and survives the reboot the install inherently needs.

The three install surfaces and their audiences:

Tool

Audience

Scope

lota-install

Player on a single machine

Guided stages, consent prompts, reboot resume

production bring-up

Operator bringing up fleet hosts, CA and verifier

Full manual reference

scripts/lota-dev-bringup.sh

Contributor iterating on a dev host

Generates a throwaway signing key. Never for production.

Running it

sudo lota-install \
    --ca-server ca.example --ca-port 8444 --ca-cert /path/to/ca.crt \
    --verifier verifier.example

CA/verifier endpoints and the trust material paths come from the operator's install instructions (see "What the operator must ship" below).

lota-install --status prints a read-only report of every stage without changing anything.

  • --yes skips the per-stage prompts,

  • --plain disables the TUI for logs and scripting,

  • --unattended is the mode a package post-install hook runs in. It implies --yes --plain, does nothing at all inside a container or image build (the host that eventually boots the image is the one bring-up belongs to), and leaves the two boot-path stages alone -- the initramfs PCR 14 lock and the kernel integrity floor -- stopping at the reboot checkpoint with the one command that finishes the job. A host that wants those unattended as well says so before installing, with /etc/lota/auto-bringup or LOTA_AUTO_BRINGUP=1; exactly 1, since a hook inherits whatever environment the transaction had and reading 0 as consent is how a machine ends up with a boot path nobody chose.

On an interactive terminal lota-install is a full-screen application (alternate screen): a stage list on the left, a details pane explaining the selected stage, and an output pane streaming what every command actually does. The initial system probe runs before the screen takeover and its per-stage results stay in the scrollback. Nothing scrolls away and the layout follows terminal resizes. Keys:

Key

Action

up/down or j/k

Select a stage

Enter

Run the selected stage (dialog explains the change and asks first)

a

Run every pending stage in order

y / n

Answer the confirmation dialog

r

Re-probe all stages

PgUp/PgDn or Ctrl-U/Ctrl-D

Scroll the output pane

q, Ctrl-C

Quit (Ctrl-C first aborts a running command)

The original scrollback is restored on exit and a one-line result (complete / reboot required / re-run to continue) is printed to the normal screen. Without a TTY, or with --plain, the same stages run as a sequential prompted flow suitable for logs and scripts.

Exit codes: 0 complete, 1 failed or blocked on missing input, 2 usage, 10 reboot required -- reboot and re-run the same command, the installer detects the finished stages from live system state and continues at the first unmet one.

There is no state file to corrupt: every done-condition is probed from the system itself (installed files, the fs-verity bit, the initramfs content, /proc/cmdline, PCR 14 via sysfs, the certificate's validity window), and the probes mirror the agent's own startup gates, so the installer cannot report green on a host the agent would refuse.

What the stages do

  1. Preflight -- TPM 2.0 device present, UEFI Secure Boot enabled, required tooling installed. Secure Boot off is a hard stop: the verifier proves it from the TPM event log and rejects hosts without it (MOK-signed custom kernels keep working). It is also the one requirement nobody but the person at the keyboard can satisfy, so the blocked stage prints a route instead of a rule: the machine as DMI names it, systemctl reboot --firmware-setup where the firmware advertises that it honours the request, the extra "restore the factory keys" step when the firmware is in setup mode, and that enabling Secure Boot leaves distribution kernels bootable. A guest is sent to its VM definition instead, since a virtual machine has no firmware menu of its own -- systemd-detect-virt decides that, so the installer keeps no list of hypervisors. Everything in that message is read off the machine; the installer names no per-vendor menu path or setup key, because those differ between firmware revisions of a single model and a confidently wrong instruction costs more than a general one.

  2. Package artifacts -- agent binary, BPF object, systemd units, udev rule and dracut module are installed. The installer does not build or download anything. Missing artifacts mean the LOTA package has not been installed yet.

  3. Operator trust material -- the BPF object's signature verifies against the operator's public key, and /etc/lota/lota.conf points the agent at that key. Fail-closed: the installer never generates a signing key on the player machine -- a locally generated key would let local malware re-sign a tampered enforcement object.

  4. Binary immutability -- enables fs-verity on the agent binary (ext4/btrfs/f2fs). On filesystems without verity (XFS, ZFS) it instead accepts a signed security.ima xattr enforced by IMA appraisal, which gives the same guarantee.

  5. Initramfs PCR14 lock -- regenerates the initramfs of every installed kernel so the PCR14 lock helper runs before any regular userspace, whichever kernel is selected at boot. It reports work to do while any installed kernel is missing the helper. Requires a reboot.

  6. Kernel integrity floor -- appends ima=on ima_appraise=fix (plus module.sig_enforce=1 / lockdown=integrity where the running kernel lacks them) to the boot entries via grubby. The floor pins the appraisal mode. Signature content stays distribution or operator-supplied (see the production bring-up guide). Requires a reboot.

  7. SELinux fence -- loads the LOTA policy module if needed and re-triggers udev so /dev/tpm* carries the LOTA-only label.

  8. Reboot checkpoint -- stops with exit 10 until the boot-chain changes are live and PCR 14 carries this boot's initramfs lock. PCR 14 only resets on a hardware reset, so this cannot be skipped. On a Secure Boot machine PCR 14 is already non-zero before any of this -- shim measures the MOK variables into it -- and the checkpoint says so rather than reporting state from an earlier install.

  9. Agent service -- enables and starts lota-agent.service and its socket.

  10. Enrollment -- the TPM proves itself to a publisher's attestation CA (credential activation) and receives a short-lived AIK certificate, one per publisher. Naming a CA here (--ca-server with --ca-cert) enrolls with it during the install, which is what an operator provisioning a fleet wants. Naming none is the normal player case and is not a blocked stage: the publisher is whoever they buy a title from, so the agent enrolls with each publisher the first time a title asks for one. Either way the running agent renews the certificate on its own against the endpoint recorded in the profile, and lota-agent --reenroll --ca-cert ... stays as a manual fallback.

    Each publisher's key is derived from that publisher's identity, so the certificates they hold carry different public keys and no two of them can name this machine from what their own CA issued. That is the whole of the guarantee, and it is worth being precise about the rest: a publisher who runs a verifier also receives this host's boot evidence -- the firmware and Secure Boot measurements, and the event log, which names the disk the host boots from. Two publishers who both run verifiers can compare what they each legitimately received and conclude it is one machine. A publisher whose titles only check tokens receives none of that, and for them the separation is complete. Choosing between the two is the privacy decision in this design; lota-agent --list-publishers says which of them this machine answers to and --forget-publisher takes one back.

Run ends with a self-check (integrity floor, fs-verity, service, certificate, and an attestation round-trip) and a plain-language summary of exactly what telemetry leaves the machine.

Who performs that round-trip depends on whether the host answers to publishers. On a machine with no publisher profiles, --verifier names the one verifier and the self-check runs a round against it; with no --verifier, the first title launch performs it. On a machine that has publisher profiles, the publishers are the agent's target list -- each with its own trust anchor, verifier and cadence -- so no single verifier stands for them and the self-check says so instead of naming one. That is not a host failing to attest: a --verifier passed alongside profiles is reported as unused, and a publisher whose titles gate on a session is reported to when one of their titles runs.

What the operator must ship

A player install needs these inputs, all fail-closed:

  • nothing for enforcement: the agent package ships the BPF object, its .sig and the public key at /usr/lib/lota/enforcement.pub, because enforcement is host-owned and signed by whoever built the package. All three are replaced together by an upgrade. A fleet that signs it with its own key re-signs the object and puts its key at /etc/lota/policy.pub, which no package owns and the agent prefers whenever it is there;

  • optionally an attestation CA endpoint (--ca-server, --ca-port) and its trust anchor (--ca-cert), to enroll during the install. Both or neither: an anchor without an endpoint has nothing to enroll against. A player install normally passes neither and lets the agent enroll with each publisher when a title first asks;

  • optionally the verifier endpoint (--verifier) for the final round-trip check;

  • compiled SELinux module (lota.pp, default /usr/share/lota/selinux/lota.pp, override with --selinux-module) on SELinux-enforcing distributions.

The RPM and DEB post-install hook drives this same stage engine (lota-install --unattended), so installing the package leaves the host-local half of bring-up already done and this command finishes the boot path. Building from a release tree instead (sudo make install) does none of it, and every stage below is then this command's work.

Titles that run under Proton or Wine

A Windows title reaches the agent through a Linux-side bridge (liblota_wine_hook.so), because a DLL inside the Wine prefix cannot open a host socket. Steam has to be told to preload it, which is one line pasted into that title's launch options -- lota-steam-setup prints the exact string, including the PRESSURE_VESSEL_FILESYSTEMS_RW entry the container manager needs to see the socket at all.

A title running inside a Proton container reaches the agent through a per-user socket, and lota-steam-setup --register-uid records the account that needs one. The agent reads that setting when it starts, so it takes effect at the next boot: the command says so before it edits anything. Nothing stops the running agent to force it -- stopping the agent spends this boot's attestation and would cost a reboot regardless.

After that boot the socket follows the login rather than the agent: it is created when the registered account logs in and released when its last session ends. lota-steam-setup --verify, run from that account, is the check -- it reports the socket as ready only when this account can open it, and prints the ownership when it cannot, which is the case an account outside the lota group lands in.

It is the only step in this document that asks anyone to type something, and it stays until Steam supports a compatibility-tool layer that composes with the player's Proton choice rather than replacing it. A publisher shipping their own launcher avoids it entirely by setting the same variables on the process they spawn. The reasoning, and the three alternatives that were examined and rejected, are in examples/cs2/README.rst.

When a title will not close

A game that asks to be protected (lota_protect_self()) is taken out of reach of every signal on the machine: the LSM passes one only from the process itself, from the agent or from the kernel. That is what stops a cheat from killing the process being measured, and it also means a hung title survives a terminal, a desktop task manager and a root shell alike.

The agent holds the identity the kernel accepts, so it delivers the signal on request:

lota-agent --terminate-protected <pid>
lota-agent --terminate-protected <pid> --force   # SIGKILL

It answers for whoever kill(2) would already have allowed -- the owner of the process, or root -- so a player ends their own game as themselves, with no sudo. Nothing else widens: only SIGTERM and SIGKILL are relayed, a process nobody protected is refused because an ordinary kill reaches it, and the agent will not end itself this way.

The exchange is that the host says so. Every relayed termination is journalled with the target, its owner and the caller, and the host reports PROTECTED_TERMINATED in its status and in every token until it reboots. A publisher then sees a session that was ended on the machine rather than a process that silently disappeared -- closing a hung game is the ordinary reason for it, and what says which process left is the protected set the token already carries.

Pausing and removing

  • Pause: sudo lota-install --pause (a wrapper over lota-agent --shutdown). The agent deliberately cannot be killed (the kill-block is the anti-tamper surface), and the graceful shutdown poisons PCR 14 before unloading -- so resume requires a reboot. That is the security contract, not a bug: same-boot re-attestation after a shutdown would let a tampered session pose as the original one.

  • Resume: sudo lota-install --resume explains that resuming is a reboot and offers to reboot now; after it the socket-activated agent starts on its own and re-measures into a fresh PCR 14.

  • Remove: take the host off the kernel floor (drop the cmdline parameters) and disable the service. The agent then refuses to run and the host simply stops attesting. The design degrades gracefully: disabled means fails attestation, never agent runs without its guarantees.

What leaves this machine

Shown by the installer at the end of every run, repeated here. Each attestation report to the operator's verifier carries:

  • TPM PCR values and a TPM-signed quote,

  • Boot event log (which includes the kernel command line and therefore disk UUIDs),

  • Agent and kernel image hashes,

  • IOMMU status,

  • Recent process-execution telemetry from the BPF layer (binary paths, hashes, PIDs, UIDs),

  • Stable device identifier derived by hashing the TPM endorsement key's public name,

  • AIK certificate whose subject is a CA-issued pseudonym.

During enrollment only, the attestation CA additionally receives the TPM's EK certificate. No file contents, browsing data or account identity are read or transmitted.