Skip to content

fix push-mode: cache UEFI event log bytes at startup to avoid post-privilege-drop failures#1223

Open
phaer wants to merge 2 commits intokeylime:masterfrom
phaer:fix/uefi-log-privileged-fd
Open

fix push-mode: cache UEFI event log bytes at startup to avoid post-privilege-drop failures#1223
phaer wants to merge 2 commits intokeylime:masterfrom
phaer:fix/uefi-log-privileged-fd

Conversation

@phaer
Copy link
Copy Markdown

@phaer phaer commented Apr 7, 2026

generate_uefi_log_evidence() was re-opening the securityfs UEFI event log by path on each attestation, which fails with PermissionDenied after run_as drops privileges in the push-model agent.

So we read and cache the raw event log bytes at startup, while still privileged. Since the UEFI event log only contains boot-time events (up to ExitBootServices), the cached bytes should stay valid for as long as the agent runs (and longer; until reboot).

Summary by CodeRabbit

  • New Features

    • Add optional in-memory caching of measured-boot and UEFI log bytes and wire cache into evidence collection to prefer cached data over on-disk reads.
  • Tests

    • Extended and added tests validating cache precedence, file-fallback behavior, and missing-path scenarios; updated test helpers to include the new cached fields.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 7, 2026

📝 Walkthrough

Walkthrough

Adds an optional in-memory cache for measured-boot (UEFI) log bytes in PrivilegedResources, initializes it at startup when a path exists, and extends evidence collection APIs to accept and prefer cached bytes over reading the log file.

Changes

Cohort / File(s) Summary
PrivilegedResources & startup cache
keylime-push-model-agent/src/privileged_resources.rs
Added public field measuredboot_ml_bytes: Option<Vec<u8>>; on new() reads measured-boot file into memory when present (warns on read failure). Unit tests updated to assert cache behavior.
ContextInfo UEFI handling
keylime/src/context_info.rs
Added uefi_log_content helper, changed generate_uefi_log_evidence and collect_evidences signatures to accept cached_uefi_bytes: Option<&[u8]>, and updated logic to prefer cached bytes over file reads. New unit tests for cache precedence and encoding.
Callsite integration
keylime-push-model-agent/src/struct_filler.rs
Passed self.privileged_resources.measuredboot_ml_bytes.as_deref() into collect_evidences call; updated test helper initialization.
Test fixtures / literals
keylime-push-model-agent/src/attestation.rs, keylime-push-model-agent/src/main.rs, keylime-push-model-agent/src/state_machine.rs
Extended test helpers and struct literals to set measuredboot_ml_bytes: None where PrivilegedResources is constructed in tests.

Sequence Diagram

sequenceDiagram
    participant Startup as Startup
    participant PR as PrivilegedResources
    participant FS as FileSystem
    participant Cache as In-Memory Cache
    participant SF as StructFiller
    participant CI as ContextInfo

    Startup->>PR: PrivilegedResources::new(measuredboot_ml_path)
    PR->>FS: read(measuredboot_ml_path)
    alt read ok
        FS-->>PR: bytes
        PR->>Cache: store bytes
    else read error / missing
        FS-->>PR: error / not found
        PR->>Cache: set None
    end

    SF->>CI: collect_evidences(..., cached_uefi_bytes)
    CI->>CI: uefi_log_content(log_path, cached_uefi_bytes)
    alt cached_uefi_bytes present
        CI-->>CI: base64(cached bytes)
    else cached absent, log_path exists
        CI->>FS: read log_path
        FS-->>CI: base64(file bytes)
    else neither
        CI-->>CI: return empty string
    end
    CI-->>SF: EvidenceData (UEFI log)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately describes the primary change: caching UEFI event log bytes at startup to prevent permission failures after privilege drop in push-mode.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

phaer added 2 commits April 7, 2026 15:50
generate_uefi_log_evidence() re-opened the securityfs event log
by path, failing with PermissionDenied after run_as drops
privileges. Cache the raw bytes in PrivilegedResources at startup
and pass them through collect_evidences(). The UEFI event log
only contains boot-time events (up to ExitBootServices) so the
cache is always valid.

Signed-off-by: phaer <phaer@phaer.org>
privileged_resources: extend the three existing tests to also assert
measuredboot_ml_bytes is None/Some with the correct content.

context_info: extract uefi_log_content() as a TPM-free helper and add
three unit tests:
- test_cache_preferred_over_file: RED without the commit (reads file,
  gets different content than the cache)
- test_cache_used_when_path_nonexistent: RED without the commit (tries
  to open a nonexistent file, returns an error)
- test_file_fallback: regression test confirming the file path still
  works when no cache is provided

Signed-off-by: phaer <phaer@phaer.org>
@phaer phaer force-pushed the fix/uefi-log-privileged-fd branch from b9ebc6b to f0cb81e Compare April 7, 2026 13:50
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@keylime-push-model-agent/src/privileged_resources.rs`:
- Around line 147-161: If the measured-boot file exists but std::fs::read()
fails, do not silently set measuredboot_ml_bytes to None while leaving
measuredboot_ml_file present; instead make the cached bytes mandatory for the
push-model path by treating a read failure as an error: when
measuredboot_ml_path.exists() is true and reading it fails, return an Err (or
otherwise abort initialization) so struct_filler never forwards a None cache
into collect_evidences(); alternatively, if you prefer to suppress UEFI
evidence, ensure you clear measuredboot_ml_file when the read fails so
struct_filler/collect_evidences() will not attempt to reopen the log later. Use
the symbols measuredboot_ml_path, measuredboot_ml_bytes, measuredboot_ml_file,
struct_filler, and collect_evidences to locate and implement the change.

In `@keylime/src/context_info.rs`:
- Around line 790-839: Add a regression test that exercises the full plumbing by
calling collect_evidences() (the higher-level path used in production) with a
nonexistent log_path and a populated cached_uefi_bytes buffer and asserting the
returned evidence includes the base64-encoded UEFI log; specifically, create a
test that supplies Some("/nonexistent/uefi_log.bin") and Some(cache_bytes) to
collect_evidences (or the public entry that invokes struct_filler ->
uefi_log_content) and verify the resulting Evidences/struct contains the
expected base64 string so the test fails if cached_uefi_bytes are not forwarded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: db282b2f-ffd7-4bec-ae90-fe411b08f5d3

📥 Commits

Reviewing files that changed from the base of the PR and between 76befd6 and b9ebc6b.

📒 Files selected for processing (6)
  • keylime-push-model-agent/src/attestation.rs
  • keylime-push-model-agent/src/main.rs
  • keylime-push-model-agent/src/privileged_resources.rs
  • keylime-push-model-agent/src/state_machine.rs
  • keylime-push-model-agent/src/struct_filler.rs
  • keylime/src/context_info.rs

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
keylime/src/context_info.rs (1)

790-839: 🧹 Nitpick | 🔵 Trivial

Add one regression that exercises the collect_evidences() cache plumbing.

These tests only cover uefi_log_content(). They still pass if keylime::context_info::ContextInfo::collect_evidences() stops passing cached_uefi_bytes into generate_uefi_log_evidence(). Please add one higher-level test with a nonexistent log_path and a populated cache that asserts the returned EvidenceData::UefiLog contains the cached base64 payload.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@keylime/src/context_info.rs` around lines 790 - 839, Add a higher-level test
that exercises ContextInfo::collect_evidences to ensure cached UEFI bytes are
passed through to generate_uefi_log_evidence: create a ContextInfo (or builder)
with log_path set to a nonexistent path and cached_uefi_bytes populated, call
collect_evidences(), find the EvidenceData::UefiLog entry in the returned
evidences, base64-encode the same cached bytes using
base64::engine::general_purpose::STANDARD and assert the EvidenceData::UefiLog
payload equals that encoded string; this will validate collect_evidences()
actually forwards cached_uefi_bytes to generate_uefi_log_evidence rather than
relying on on-disk reads.
keylime-push-model-agent/src/privileged_resources.rs (1)

147-157: ⚠️ Potential issue | 🟠 Major

Make the UEFI byte cache mandatory once the log is available.

If std::fs::read() fails here, measuredboot_ml_file can stay populated while measuredboot_ml_bytes becomes None. keylime-push-model-agent/src/struct_filler.rs::FillerFromHardware::get_evidence_handling_request_final only forwards measuredboot_ml_bytes into keylime::context_info::ContextInfo::collect_evidences(), and keylime/src/context_info.rs::uefi_log_content() then falls back to reopening log_path. That revives the same post-run_as PermissionDenied path this PR is meant to remove. Please either fail initialization here or suppress UEFI evidence by clearing measuredboot_ml_file too.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@keylime-push-model-agent/src/privileged_resources.rs` around lines 147 - 157,
The cached UEFI bytes are being optionalized on read failure while the
associated measuredboot_ml_file remains set, which allows later code paths
(FillerFromHardware::get_evidence_handling_request_final ->
ContextInfo::collect_evidences -> uefi_log_content) to attempt reopening the
file with insufficient permissions; fix by making the UEFI cache mandatory or
disabling the file reference on read error: either propagate an initialization
error when std::fs::read(measuredboot_ml_path) fails (failing startup) or, if
you prefer to suppress UEFI evidence, clear measuredboot_ml_file (set it to
None) whenever measuredboot_ml_bytes is None so both the bytes and file handle
stay consistent; update the code around measuredboot_ml_bytes and
measuredboot_ml_file to enforce that invariant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@keylime-push-model-agent/src/privileged_resources.rs`:
- Around line 147-157: The cached UEFI bytes are being optionalized on read
failure while the associated measuredboot_ml_file remains set, which allows
later code paths (FillerFromHardware::get_evidence_handling_request_final ->
ContextInfo::collect_evidences -> uefi_log_content) to attempt reopening the
file with insufficient permissions; fix by making the UEFI cache mandatory or
disabling the file reference on read error: either propagate an initialization
error when std::fs::read(measuredboot_ml_path) fails (failing startup) or, if
you prefer to suppress UEFI evidence, clear measuredboot_ml_file (set it to
None) whenever measuredboot_ml_bytes is None so both the bytes and file handle
stay consistent; update the code around measuredboot_ml_bytes and
measuredboot_ml_file to enforce that invariant.

In `@keylime/src/context_info.rs`:
- Around line 790-839: Add a higher-level test that exercises
ContextInfo::collect_evidences to ensure cached UEFI bytes are passed through to
generate_uefi_log_evidence: create a ContextInfo (or builder) with log_path set
to a nonexistent path and cached_uefi_bytes populated, call collect_evidences(),
find the EvidenceData::UefiLog entry in the returned evidences, base64-encode
the same cached bytes using base64::engine::general_purpose::STANDARD and assert
the EvidenceData::UefiLog payload equals that encoded string; this will validate
collect_evidences() actually forwards cached_uefi_bytes to
generate_uefi_log_evidence rather than relying on on-disk reads.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: de7056c9-f099-4821-ab01-949d75939ee7

📥 Commits

Reviewing files that changed from the base of the PR and between b9ebc6b and f0cb81e.

📒 Files selected for processing (6)
  • keylime-push-model-agent/src/attestation.rs
  • keylime-push-model-agent/src/main.rs
  • keylime-push-model-agent/src/privileged_resources.rs
  • keylime-push-model-agent/src/state_machine.rs
  • keylime-push-model-agent/src/struct_filler.rs
  • keylime/src/context_info.rs

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 7, 2026

Codecov Report

❌ Patch coverage is 46.87500% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.53%. Comparing base (76befd6) to head (f0cb81e).

Files with missing lines Patch % Lines
keylime/src/context_info.rs 38.09% 13 Missing ⚠️
...ylime-push-model-agent/src/privileged_resources.rs 70.00% 3 Missing ⚠️
keylime-push-model-agent/src/struct_filler.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
Flag Coverage Δ
e2e-testsuite 38.99% <0.00%> (-0.09%) ⬇️
upstream-unit-tests 66.09% <71.42%> (-0.05%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
keylime-push-model-agent/src/attestation.rs 45.78% <ø> (ø)
keylime-push-model-agent/src/main.rs 24.69% <ø> (ø)
keylime-push-model-agent/src/state_machine.rs 17.36% <ø> (ø)
keylime-push-model-agent/src/struct_filler.rs 20.24% <0.00%> (-0.09%) ⬇️
...ylime-push-model-agent/src/privileged_resources.rs 60.00% <70.00%> (+10.00%) ⬆️
keylime/src/context_info.rs 45.77% <38.09%> (-0.22%) ⬇️

... and 5 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

phaer added a commit to phaer/nixos-android-builder that referenced this pull request Apr 13, 2026
Link each local patch to its upstream PR (or tracking issue) so the
provenance of each is obvious from the package files alone:

  - 0001 elparsing        → keylime/keylime#1878
  - 0002 tpm-relevant-pcrs → keylime/keylime#1879
  - 0003/0004 dual-mapping → keylime/keylime#1880 (issue, no PR yet)
  - keylime-agent 0001    → keylime/rust-keylime#1223
phaer added a commit to phaer/nixos-android-builder that referenced this pull request Apr 13, 2026
Link each local patch to its upstream PR (or tracking issue) so the
provenance of each is obvious from the package files alone:

  - 0001 elparsing        → keylime/keylime#1878
  - 0002 tpm-relevant-pcrs → keylime/keylime#1879
  - 0003/0004 dual-mapping → keylime/keylime#1880 (issue, no PR yet)
  - keylime-agent 0001    → keylime/rust-keylime#1223
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant