What still fails, and operational constraints
What still fails after bring-up
The most common failures, with the gate that produced them:
Kernel anti-tamper prerequisites are not satisfied. Checkcat /sys/kernel/security/lockdown(must show[integrity]or[confidentiality]),cat /sys/module/module/parameters/sig_enforce(must beY), andgrep -oE 'ima_appraise=\w+' /proc/cmdline(must reportenforceorfix).Agent binary (/proc/self/exe) is not protected against offline tampering. On ext4/btrfs/f2fs re-runfsverity enableon/usr/bin/lota-agent; the verity merkle root is bound to the inode, so re-installs invalidate the bit and the bring-up script re-enables on every run. On XFS/ZFS (no verity) re-sign the binary into itssecurity.imaxattr and confirmima_appraise=enforce; the agent accepts the signed xattr as equivalent.BPF object signature verification failed. The object and the key no longer belong to each other. Apolicy_pubkeyline naming a file that is no longer there does not cause this: the agent treats a key that has stopped resolving as absent and falls back, so a host an older installer configured keeps enforcing after an upgrade moved the key. The packaged key at/usr/lib/lota/enforcement.pubalways matches the packaged object, so this means a key at/etc/lota/policy.pub(or one named bypolicy_pubkey) is taking precedence and did not sign this object: move it aside to fall back to the packaged one, or re-sign the object with the fleet key.Failed to load AIK metadata: Key has been revoked. The TPM has a persistent AIK but the operator wiped/var/lib/lota. Either restore the metadata backup or evict the AIK handle and reboot so the agent re-provisions clean.PCR14 holds an unexpected value. Cold reboot. PCR14 only resets on hardware reset; warm reboot keeps the value. On UEFI Secure Boot this also appears when the90lotainitramfs lock did not run: shim leaves the MOK measurement in PCR14, and without the lock recording that baseline at/run/lota/pcr14_baselinethe agent cannot anchor its derivation. Confirm the dracut module is installed and the initramfs was rebuilt (see Manual reference: host gates, section 5), then cold reboot.The agent is paused on this host. Its boot commitment was spent by a shutdown requested here --lota-agent --shutdown,lota-install --pause, or asystemctl stopthat ran the unit'sExecStop. PCR 14 cannot be re-extended once poisoned, which is what makes a pause visible to every relying party. Reboot to attest again.lota-steam-setup --register-uiddeliberately does not stop the agent for this reason: its setting takes effect at the next boot, and the command says so before it edits the configuration.Startup policy refused before the boot commitment was spent. A path or PID named by the enforcement policy could not be resolved -- most often a--trust-libor--allow-veritypath the unit's sandbox hides, sinceProtectSystem=strictandPrivateTmp=yesmean the agent does not see/tmpor/var/tmpthe way the shell that wrote the drop-in does. The message names the path. Nothing has been spent: PCR 14 still holds the initramfs lock value, so correcting the policy and starting the unit again brings the host up in the same boot, with no reboot needed.This is not the lota-agent build PCR 14 committed to when the host booted. The binary being run is not the one the register commits to, which is the expected answer after replacing the binary without rebooting, or when running a second build by hand. Install the build and cold reboot; PCR 14 is not rewritable while the host is up. This is a refusal by design, not a TPM fault.PCR14 no longer holds the value lota-agent extended in this boot session. Something outside the agent extended PCR 14. Cold reboot, then audit what reached/dev/tpmrm0.This publisher's attestation key was created before each publisher got a key of its own. The host was enrolled by a build that gave every publisher the same TPM key. A key cannot be rewritten in place, so the agent refuses to attest with it and the fix is one command per publisher:sudo lota-agent --reenroll --ca-cert <their anchor>. The device pseudonym is derived from the key and moves with it, so each publisher's verifier sees a device it has not met and re-establishes its baseline. Nothing has been spent: the refusal happens before the boot commitment, so the host comes up in the same boot once the enrollment is done.
The unit does not restart into either of the last two: the agent exits 78
(EX_CONFIG) for a state only an operator can clear, and
RestartPreventExitStatus=78 leaves the unit failed with the reason in the
journal.
Counters printed by these messages come from TPM2_ReadClock and are the
values tpm2_readclock reports, so they can be checked against the machine.
That is also where the agent takes the counters it records for itself, the
ones that tell a restart within a boot from a hardware reset. Counters carried
by a quote will not match them, and are not meant to: a TPM offsets clock,
resetCount and restartCount in a signed attestation by an amount unique
to the signing key, so with per-publisher keys there is no single quoted
reading to compare against. Nothing binds either reading into PCR 14 -- the
boot commitment names the agent binary alone -- so the counters are diagnostic
here, not evidence.
Threat model implications of the dev path
scripts/lota-dev-bringup.sh lays down a self-signed operator key on disk in
/etc/lota. That key is the trust root for every BPF object the agent loads
on this host. An attacker with root can re-sign a tampered BPF object with the
same key and the agent will accept the load.
Production deployments treat the signing key as a sealed infrastructure
artefact: kept off-host, rotated through the operator's PKI, and never present
in /etc/lota on a live machine. The bring-up script's key generation is
explicitly developer-only.
Operational constraints
Agent restart requires reboot
The agent's lota_task_kill LSM hook blocks SIGTERM and SIGKILL
delivered from any other task -- including PID 1 -- because the hook treats the
agent itself as a protected target. This is the load-bearing surface that
prevents a local-root attacker from killing the agent out of band, dropping the
BPF coverage, and swapping a tampered binary into place before the next
attestation.
This applies in enforce mode only. The hook reads the enforcement mode the
way every other gate does: in monitor and maintenance the signal is
reported and delivered, so an agent an operator is evaluating stops with
kill, timeout or whatever supervisor started it. In enforce the
refusal stands and the way to stop the agent is lota-agent --shutdown.
Either way the signal is logged with the sender's pid, uid and command, so a
supervisor that cannot stop the agent is diagnosable from the journal.
What the boot commitment costs does not change with the mode, because it is a
property of how the agent stopped. A delivered SIGTERM runs the same clean
shutdown as --shutdown: PCR 14 is poisoned, and the next agent start in that
boot says the host is paused. A SIGKILL is a dirty stop, leaves PCR 14
holding the boot commitment, and a new agent starts in the same boot -- which is
what makes an evaluation loop practical, and why the dirty-shutdown case is
covered by fs-verity on the agent binary instead.
The trade-off is that systemctl restart lota-agent does not bring the
host back the way it does for other units. Stopping works: ExecStop is
/usr/bin/lota-agent --shutdown, which sends a privileged IPC command that
exits the daemon loop cleanly, and a caller running as the agent's own uid is
authorised on a default install. What does not work is the start half.
Stopping the agent poisons PCR 14, deliberately. The boot commitment cannot be re-extended once spent, so an agent started again in the same boot refuses at self-measurement and says the host is paused. That is the property that makes a pause visible to every relying party.
So the two supported paths are:
Stop now, resume at a reboot.
systemctl stop lota-agent,lota-agent --shutdownandlota-install --pauseall take the same route and all end enforcement immediately. Attestation resumes at the next boot and not before. This is the intended way to pause a host.Cold reboot. If the IPC path is unreachable (agent hang, socket gone, kernel deadlock) the only remaining recovery is to reboot the host. There is no kill-bypass for PID 1 and there never will be: every grace window would be an attack surface for an init-domain compromise. Operators planning updates therefore schedule them alongside a regular maintenance reboot.
A configuration change that only the daemon's own state depends on does not
need either path: systemctl try-reload-or-restart lota-agent lota-attest
picks up a newly added publisher without stopping anything. Settings the agent reads once at startup,
such as container_listener_uid, take effect at the next boot instead.
The socket that setting asks for is a separate matter from the setting itself.
/run/user/<uid> belongs to a login, so the agent creates
/run/user/<uid>/lota/lota.sock when the registered user logs in and
releases it when the last session closes; no restart is involved, and none
should be attempted, since stopping the agent spends the boot's PCR 14
commitment. The socket is handed to the lota group at mode 0660, so an
account outside that group cannot open it even though it is there.
lota-steam-setup --verify, run from the user's own session, reports both
halves: whether the socket exists and whether that account can open it. When
it cannot, the tool prints the socket's ownership and points at the agent's
own message (journalctl -u lota-agent | grep chown), because a socket the
agent could not hand to the group looks identical from the outside to one it
did. When the socket is absent it names the two things that produce one --
a registered UID and a boot since it was registered -- and does not suggest
restarting the agent.
Continuous attestation
lota-agent.service only enforces locally (BPF LSM, PCR14 commitment); it
does not attest. A deployed host proves its state through a second unit,
lota-attest.service, which runs lota-agent --attest fire-and-forget and
renews the CA-issued AIK certificate before it expires. It is a separate unit
on purpose: the attestation loop is network-facing, so isolating it from the
enforcement daemon keeps that security core on a tight seccomp and capability
profile. The attest unit itself runs with no effective capabilities at all
(SecureBits=noroot-locked with an empty ambient set), reaches the TPM only
through DeviceAllow, and cannot load BPF or disable enforcement. A
compromise of the attest process at worst stops fresh attestations, which the
verifier sees as staleness and marks untrusted.
The unit arms a 60 s systemd watchdog: the attest loop pings it on a cadence
independent of attest_interval, so a loop wedged on the TPM or a stalled
TLS socket misses the deadline and systemd restarts it.
A round that fails names the stage it failed at in the journal -- setting up TLS, reaching the verifier, building or sending the report, or the verdict itself -- alongside the failure count and the backoff:
Attestation round failed at connecting to the verifier: Connection refused
Attestation FAILED for verifier.example:9443 (attempt 1, backoff 10s)
The stage is what separates a host that cannot reach its verifier from one the verifier is refusing, and it is reported whether or not anything is reading the loop's progress output.
Which unit a title talks to
lota-agent.service owns /run/lota/lota.sock, and it is the only unit
that does. It is the always-on one, it is what lota-agent.socket activates,
and it holds everything about the machine a title asks after: the BPF LSM
state, the enforcement policy digest a token carries, the Secure Boot and IOMMU
findings, and the publisher list a title selects from.
What it does not hold is a verifier's verdict, so the attestation loop connects to that same socket as a local peer and trades state with it once per round -- verdicts out, sessions and enrollment requests in. That exchange is refused unless the peer is the same agent binary running as the same user, and it is never something an integrator sends: the SDK has no such call.
Two consequences worth knowing:
Stopping ``lota-attest.service`` does not stop a title from getting an answer. The socket stays up and enforcement keeps running; what goes stale is the verdict, which is exactly what a verifier is watching for. Session-gated publishers stop being reported to, which is the safe direction.
Stopping ``lota-agent.service`` takes the socket down with it. A title gets a connection failure rather than a wrong answer, and the attest loop keeps attesting and retries the link every 30 s. Session gating and runtime enrollment are off for that window, and the loop says so in the journal.
The verifier, port, CA certificate and cadence come from
/etc/lota/lota.conf (server, port, ca_cert, attest_interval);
no attestation flags are hardcoded in the unit. A zero attest_interval
means this host never chose a cadence: with a [profile] list it attests to
every configured publisher at the default 300 s, and only a host with no
profile at all falls back to attesting once to server and exiting. A
non-zero value has to fall between the floor and the ceiling the agent prints
in --help; the agent refuses to start on anything else, since an interval
past the ceiling mints tokens that sit outside every relying party's freshness
window.
ca_cert must point at a path the hardened unit can read. Both units run
with ProtectSystem=strict and with /home and /root inaccessible, so
a certificate left in an operator home directory (the --ca-cert ~/tls.crt
used for a manual --enroll) is invisible to them. Copy the verifier CA certificate under
/etc/lota (root-owned, the unit mounts it read-only) and point ca_cert
there, for example:
sudo install -m 0644 verifier-ca.crt /etc/lota/verifier-ca.crt
# then in /etc/lota/lota.conf: ca_cert = /etc/lota/verifier-ca.crt
A host that answers to more than one publisher lists them as [profile "name"]
sections instead. Each section names the attestation CA it enrolls against, the
trust anchor that CA is verified with, the verifier it reports to, and
optionally its own cadence; ca_port and verifier_port default to the
same ports as the top-level keys. A profile missing the CA, the anchor or the
verifier is refused at load, and every anchor has to satisfy the same
readability constraint as the top-level ca_cert above.
Every port named here has to carry the SELinux label on an enforcing host.
The confined agent may connect only to ports labelled lota_port_t;
lota-install labels the endpoints it was given, and one added afterwards --
a second publisher, a verifier moved to another port -- needs:
sudo semanage port -a -t lota_port_t -p tcp <port>
Without it every round fails with Permission denied against a server that
is running and that a shell on the same host can reach. The agent names the
port and this command when it happens; see selinux/README.rst for why
the labels are assigned one port at a time.
verifier = none is how a profile says that publisher runs no verifier and
checks the tokens their titles fetch in their own backend. Nothing is reported
to them and this host holds no verdict of theirs, so their titles read the
token, not the attested bit. Issuance follows that: the attested gate
applies to a publisher who runs a verifier, and a token-only publisher's gate
is that the host can quote at all -- enrolled, AIK loaded, TPM not locked out.
The token carries LOTA_STATUS_TOKEN_ONLY so the backend knows the attested
bit is not a verdict of theirs, and a freshness window the agent bounds itself
(the default cadence, since there is no round to take one from); the profile still enrolls, holds its own
AIK and renews that key's certificate, which is what the publisher's backend
chains a token to. It has to be said rather than left out -- an omitted
verifier stays a refused config, so a typo cannot turn a publisher who
expects reports into one who silently receives none -- and verifier_port
must not accompany it.
Adding a publisher does not have to be a text edit. lota-agent
--add-publisher writes the section, which is how a game's installer registers
the publisher it ships for:
sudo lota-agent --add-publisher ca.studio.example --ca-port 8444 \
--ca-cert /etc/lota/studio.pem \
--server verifier.studio.example --port 8443 \
--publisher-name studio
--server is what names that publisher's verifier, and leaving it out is how
the token-only publisher is written: the section gets verifier = none and no
port, and nothing is reported to them from this machine. The command says which
of the two it wrote, since the difference is whether any evidence leaves the
host:
sudo lota-agent --add-publisher ca.studio.example --ca-port 8444 \
--ca-cert /etc/lota/studio.pem --publisher-name studio
The rest of the file is copied through untouched and the section is appended,
so whatever the operator put there survives. A publisher is its trust anchor
rather than its label: adding the same anchor again is a no-op whichever name
it carries, and a label already spoken for by a different anchor is refused
with a note to pass another --publisher-name.
The writer holds itself to the parser's rules. A label is 1 to 63 characters with no control characters and no quote -- the quote is what ends the section header -- and the hosts and the trust anchor may carry no control character, no leading or trailing space and no line longer than the parser reads. A value that breaks one of those is refused by name, with the character at fault, and nothing is written: the alternative is a command that reports success and leaves a file the next start cannot load. The trust anchor must be an absolute path for the same reason, since that is what the parser accepts.
And it reads the result back before it replaces the file. The text already
in lota.conf is copied through unexamined, so a file that had stopped
parsing for some other reason -- a hand edit, a package that half-replaced it
-- would be appended to and installed, and the host would come up with no
publishers and no enforcement settings at all. Nothing reaches that state
through the command line, because every invocation loads the configuration
before it runs a verb and refuses on a file that does not parse, naming the
line. The writer does not lean on that: it loads the replacement with the
parser before the rename and leaves the live file untouched if the result does
not read back.
It records no consent. Nothing enrols with the publisher until somebody at
the machine runs the --allow-publisher command it prints. That separation
is deliberate: an installer must not be able to agree, on the player's behalf,
to a publisher holding an attestation key on their hardware.
A running host has to be told, and two units read this file. The daemon
answers titles from the publisher list it read when it started, so one added
since is unknown to it and a title naming it is refused with
LOTA_ERR_UNKNOWN_PROFILE. The attestation loop builds its target list the
same way, so a publisher it has not been told about is never reported to --
which shows up as a verifier that simply never hears from the host. One command
covers both:
sudo systemctl try-reload-or-restart lota-agent lota-attest
Both take SIGHUP: the daemon re-reads lota.conf and hands the new list to
the socket, and the loop rebuilds its targets between rounds, reporting to the
new publisher immediately in case a title of theirs is already running. Neither
touches enforcement, PCR 14 or any existing enrollment, and a publisher that
was already there keeps its cadence, its session state and its failure backoff.
try- is what makes it one command on every host: a unit that is not running
-- the loop stays inactive until the first enrollment -- is skipped. It is the
command --add-publisher prints alongside the consent line. A rebuild that
fails leaves the previous list in place.
Every key below a section header belongs to that section, so the top-level keys
go above the first profile and nothing top-level may follow one.
lota-agent --dump-config prints profiles last for the same reason, which is
also what makes its output loadable again. See configs/lota.conf.example
for a worked pair of profiles.
The section name is a label for the operator. A profile is identified by its trust anchor's public key, so a publisher moving their CA to another address keeps the same profile, and two publishers sharing a hostname cannot collide. Publisher policy stays with the publisher: a profile grants no publisher any say over this host's enforcement.
The list is what the attestation loop reports to. Every profile's verifier gets
its own report on that profile's cadence, signed with that publisher's own AIK,
and each profile carries its own failure state -- one unreachable verifier
backs off its own reporting and leaves the others on schedule. Since the list
replaces the single verifier rather than adding to it, --server and
--pin-sha256 are refused while profiles are configured, as is a top-level
pin_sha256; each profile is anchored by its own ca_cert.
A profile pins its verifier's certificate with its own pin_sha256 key, the
64-character SHA-256 fingerprint that verifier presents. Pinning is per
publisher because the certificate is: one fingerprint matches at most one
verifier, so a host-level pin would authenticate the publisher it was measured
against and fail every other at TLS time, before any verdict. A profile that
states no pin trusts its ca_cert alone, which is the default.
A title says which publisher it plays for, and gets that publisher's answers.
It names the profile by the lowercase hex SHA-256 of that publisher's CA trust
anchor SubjectPublicKeyInfo -- the identity the profile directory is named
after, which the publisher knows about their own CA -- through
publisher_profile in struct lota_connect_opts or
struct lota_ac_config. The connection's tokens are then signed by that
publisher's AIK, and the attested state it reads is that publisher's verifier's
verdict rather than every publisher on the host agreeing. Naming a publisher
this machine holds no enrollment for fails the connection: handing a title
another publisher's evidence under its own name would be worse than telling it
plainly.
A title that names nobody -- which is every enterprise integration, where the
host has one publisher -- gets the first profile's token and the host-wide
answer: attested only while every configured publisher is satisfied, with
the window closing at the earliest of theirs. Publishers who run no verifier
are left out of that answer, since a host holds no verdict for a publisher it
never reports to. On a host where no publisher runs one, that answer carries
LOTA_FLAG_TOKEN_ONLY so a title reads "nothing is verified here, check the
token you fetched" rather than "this machine failed".
A profile reports only while a title of its publisher is running. That is
what reporting selects, and session is a profile's default: a report is
the only thing that leaves the machine, and a verifier receiving one every few
minutes from boot to poweroff learns when the player's machine is on, for a
game that is closed. A session is a title's connection to the agent, so it ends
when the process does, whether it exited or was killed. Set
reporting = continuous on a profile whose fleet the operator owns and whose
continuous stream is the point; the single-verifier configuration keeps that
behaviour unchanged.
Enforcement and the PCR 14 boot commitment are never gated. They are local, they send nothing, and they are what lets a session's first quote still prove the whole boot-to-now window: the quote is a fresh signed read of state that already existed. On-demand enforcement would prove nothing, which is why only reporting follows the session.
Neither is the enrollment ceremony. Every configured publisher is enrolled with, has its AIK rotated and its certificate renewed on the host's cadence, whether or not a title of theirs is running. Waiting for a session would leave a session-gated publisher unreachable: a title cannot select a publisher this machine never enrolled with, so the session that would trigger the enrollment could never be opened. What the gate withholds is the report, not the key.
While no title of a publisher's is running, that publisher has no live verdict: the agent stops reporting to them and reports the host as not attested for them. A title that names no publisher reads the host-wide answer, which is now every currently reporting publisher agreeing -- and not attested when nothing is reporting at all, since nothing is being checked.
A title that names no publisher also opens no session, so it does not start
anybody reporting either. On a host where every profile is session-gated, which
is the consumer default, such a title reads not-attested for as long as it is
the only one running. That is deliberate -- a session is a title saying which
publisher it plays for, and reporting follows it -- and it makes naming the
publisher part of integrating, not an optimisation. The anti-cheat producer
reference takes --publisher; see examples/anticheat-integration.rst.
Nothing enrolls with a publisher until somebody here agrees to it. An
attestation key is a stable handle that publisher can recognise this machine
by, so the decision to hand one out is recorded before the key exists, in the
profile directory. lota-agent --allow-publisher <hex> writes it, naming the
publisher by the SHA-256 of their CA anchor's SubjectPublicKeyInfo -- the same
identity everything else uses. lota-agent --enroll records it too: somebody
with root named that CA and asked for the key, which is the same decision made
a different way, and it keeps one rule for the agent to enforce.
A title that selects a publisher nobody has agreed to is refused with a
distinct error (LOTA_ERR_CONSENT_REQUIRED, readable through
lota_connect_last_error()) rather than a generic failure, because it is a
screen to show the player rather than a fault to report. Whatever shows that
screen calls --allow-publisher when they accept.
What this machine holds for whom is inspectable, and revocable.
lota-agent --list-publishers shows every publisher with anything stored
here: when it was agreed to, where it enrolled, which TPM handle holds its
attestation key and how much validity that key's certificate has left. It ends
with what the machine has room for -- the keys held, the TPM's own persistent
capacity and this build's maximum -- so the ceiling is readable before it is
met; Platform support covers what sets it.
lota-agent --forget-publisher <hex> destroys that key and deletes the rest,
in that order -- a key with no directory left to name it would be worse than
either state alone, so nothing is deleted if the eviction fails. An identity
this machine never answered to is refused: a mistyped identity would otherwise
confirm a removal that did not happen while the real publisher stayed enrolled.
Forgetting is about the identity, not about refusing the publisher. A profile
still in lota.conf can be agreed to again, and enrolls with a new key
that the old evidence cannot be linked to.
Enrollment with a profile's publisher is a runtime action. A profile that
names a CA (ca, ca_port, ca_cert) and has never enrolled is
enrolled by the agent itself: when the loop first reaches that publisher, and
immediately when a title selects it, which is the moment that matters on a
player's machine. Until it completes, that publisher's status is not attested
and the agent sends no report to their verifier -- evidence with no certificate
to chain is refused anyway. A failing CA backs off rather than being retried
every round, and the other publishers keep their cadence throughout.
Operator fleets that enroll at install time are unaffected: lota-agent
--enroll writes the same record, and a profile that already has one is never
re-enrolled. An enrollment token cannot be presented on the runtime path (a
publisher admitting players has no way to hand each of them a secret in
advance), so a CA that requires one has to be enrolled against with
--enroll --enroll-token-file.
First enrollment stays operator-driven. lota-attest.service carries
ConditionDirectoryNotEmpty=/var/lib/lota/profiles and stays inactive until
the operator's first lota-agent --enroll creates a profile there (the
directory itself ships with the package, so only its contents say anything);
afterwards the loop renews the certificate automatically. Start it after the first enrollment
(or it activates on the next boot):
sudo systemctl enable --now lota-attest.service
Installing the package enables nothing. lota-install enables the units it
brings up -- the agent socket, the enforcement daemon and the attestation
loop -- so a host that has not been through bring-up runs neither, and a
dnf install on a machine nobody is sitting at cannot start enforcing on
its own.
VM testing caveats
The supported development environment is a KVM guest with a swTPM backend attached over TIS. Two behaviours diverge from bare metal, and the second is one the agent's startup gates treat as an integrity violation unless the operator works around it.
swTPM persists state across guest reboots. The TPM resource manager runs as a host process backed by an NV state file. A
sudo rebootinside the guest does not reset the TPM and evensudo virsh destroy <machine> && sudo virsh start <machine>from the host keeps the sameresetCountunless the libvirt XML carries<backend ... persistent_state='no'/>or swTPM is started with--flags startup-clear. The guest's PCR14 resets to all-zero on each Startup(CLEAR) butresetCountdoes not advance.A guest that boots with the
90lotadracut module comes up normally anyway: the helper locks the cleared register, the agent finds the lock value and extends its commitment onto it exactly as on hardware. What the frozen counter costs is coverage, not availability -- the attributions that key off the counter moving (a cold boot the agent did not see, a rolled-back TPM state file) cannot be exercised on such a guest, so those paths are hardware orpersistent_state='no'territory.The state that does survive is the TPM's: a guest reused across agent builds carries the earlier run's persistent AIK and the witness that names it. To start a test run from a clean slate, wipe the witness and evict the persistent AIK.
lota-attest.serviceruns an agent of its own and rewrites the witness on its next round, so it has to stop with the daemon:sudo systemctl stop lota-agent.service lota-agent.socket \ lota-attest.service sudo find /var/lib/lota -mindepth 1 -delete for h in 0x81010002 0x81010003 0x81010004 0x81010005 \ 0x81010010 0x81010011 0x81010012 0x81010013 \ 0x81010014 0x81010015 0x81010016 0x81010017; do sudo tpm2_evictcontrol -C o -c "$h" 2>/dev/null || true done
The repo is virtiofs-mounted read-only at
/mnt/<dir>.sudo make installrecurses into thealltarget through theinstall: check-version-tag allprerequisite, somakewill try to write dependency files tobuild/in the current working directory and fail withEROFSon the virtiofs mount. Pass the build directory explicitly on the same invocation:sudo make BUILD_DIR=/var/tmp/lota-build install
sudo make installdoes not load the SELinux module. The install rule landslota.ppunder the source tree but does not callsemodule -i. After any change to selinux/lota.te, rebuild the module on the host (the in-tree selinux/Makefile writes totmp/in the cwd, which the read-only virtiofs blocks), then load the package inside the guest:# [host] cd selinux && make && sha256sum lota.pp # [guest] sudo semodule -i /mnt/<dir>/selinux/lota.pp
Verify the rule landed with
sesearch -A -s lota_agent_t ...before retrying the agent. The stock policydontaudits many reads that the agent legitimately needs (e.g. kallsyms, securityfs), so denials may be silent: runsudo semodule -DBbefore reproducing to surface them, thensudo semodule -Bto re-enable./usr/bin/lota-agentmust carrylota_agent_exec_t. A freshmake installwrites the file with the defaultbin_tlabel on systems where the in-treelota.fchas not been loaded yet; without the executable type,init_tdoes not transition tolota_agent_tat exec and the daemon runs with no TPM, BPF, or/etc/lotaaccess. Restore the label after install:sudo restorecon -v /usr/bin/lota-agent ls -lZ /usr/bin/lota-agent # expect the type lota_agent_exec_t; the SELinux user in front of it is # system_u from a package and unconfined_u from a hand install, # and restorecon leaves either alone