Skip to content

Add vmclock backend for VM UBE detection, preferring it over SysGenID - #3418

Draft
dougch wants to merge 11 commits into
aws:mainfrom
dougch:vmclock
Draft

Add vmclock backend for VM UBE detection, preferring it over SysGenID#3418
dougch wants to merge 11 commits into
aws:mainfrom
dougch:vmclock

Conversation

@dougch

@dougch dougch commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Description of changes:

AWS-LC detects VM Uniqueness Breaking Events (UBE, formerly Snapsafe) on Linux so the DRBG can reseed after a VM snapshot/clone/restore. Detection previously relied solely on the Amazon-proprietary /dev/sysgenid interface. This change adds support for the non-proprietary vmclock interface (/dev/vmclock0, see the vmclock specification) and prefers it when available, so the ecosystem can move off SysGenID.

Behavior:

  • At init, vmclock is tried first. If /dev/vmclock0 is absent, or present but not usable by this process (EACCES on a root-only node, mmap failure, bad magic, or the generation-counter flag unset), we fall back to /dev/sysgenid. If neither is usable, VM UBE detection degrades to "not supported" rather than hard-failing — a hard failure would also disable the independent fork detection and force the DRBG to reseed on every request.
  • vmclock's generation counter is read using the seqlock protocol from the specification, with acquire fences and a bounded retry count so a wedged or corrupt seqcount cannot spin RAND_bytes forever.
  • The VM UBE generation number is widened from uint32_t to uint64_t to match vmclock's counter width; the SysGenID path zero-extends.

The new crypto/ube/vmclock_abi.h mirrors the kernel's vmclock ABI and uses static asserts to pin the struct size and the offsets we dereference, so an accidental edit fails the build rather than silently shifting the counter field.

Call-outs:

  • SysGenID support is retained as a fallback and is not removed; on kernels that drop it, it simply becomes an unused code path handled by the vmclock preference.
  • Test wiring is split: the single AWSLC_VM_UBE_TESTING build guard now additionally sets a per-backend flag (AWSLC_TEST_SYSGENID / AWSLC_TEST_VMCLOCK) selected by -DTEST_SYSGENID_PATH / -DTEST_VMCLOCK_PATH, so a single-backend build only creates the stand-in file for the backend under test and never touches the real /dev node for the other. Because vmclock is preferred at runtime, exercising the SysGenID path (and the vmclock→SysGenID fallback) requires a -DTEST_SYSGENID_PATH-only build.
  • The value round-trip and seqlock tests are DISABLED_ by default: they mutate the shared stand-in device file that every RAND_bytes call in the suite reads, so they cannot run interleaved. CI runs them in a dedicated single-process invocation via --gtest_also_run_disabled_tests.

Testing:

run_posix_tests.sh and run_fips_tests.sh now build each backend in a separate configuration and run its isolated device-mutating tests. Locally, both the vmclock and SysGenID backend builds compile cleanly and all VM UBE / UBE tests pass, including the isolated seqlock, torn-read, and graceful-degradation cases.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license.

dougch added 5 commits April 16, 2026 16:21
Generate HTML API docs from header comments using the existing
doc.go tool (inherited from BoringSSL) and publish to gh-pages.

Docs are deployed to the /api/ subdirectory to allow doxygen
or other doc generators to coexist in future PRs.

Workflow only triggers on changes to public headers, the doc
generator, its config, or the workflow itself.

Add missing preamble comments to 6 headers (base64.h, dsa.h,
kdf.h, lhash.h, opensslconf.h, pkcs8.h) so the doc generator
produces descriptions for all headers in the index page.
Add support for the non-proprietary vmclock interface (/dev/vmclock0)
for VM UBE detection, preferred over /dev/sysgenid when available and
falling back to it otherwise (V2082724891). SysGenID is retained, not
removed. The generation counter widens to uint64_t and vmclock is read
via the spec's seqlock protocol with bounded retries. Test wiring gains
a per-backend flag so single-backend builds only touch their own
stand-in file; CI builds each backend separately.
New exported symbol added by the vmclock backend. Registered as
AWS_LC_1.0 PRIVATE alongside the other CRYPTO_get_vm_ube_* symbols and
the .map regenerated via util/generate_version_script.
@github-actions

Copy link
Copy Markdown
Contributor

🔒 Security ReviewView Report

Please review before merging.

@codecov-commenter

codecov-commenter commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 51.26050% with 58 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.19%. Comparing base (1ab8a2c) to head (cb63369).

Files with missing lines Patch % Lines
crypto/ube/vm_ube_detect.c 27.39% 53 Missing ⚠️
crypto/ube/ube.c 69.23% 4 Missing ⚠️
crypto/ube/vm_ube_detect_test.cc 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3418      +/-   ##
==========================================
- Coverage   78.20%   78.19%   -0.02%     
==========================================
  Files         695      695              
  Lines      124271   124348      +77     
  Branches    17265    17282      +17     
==========================================
+ Hits        97186    97233      +47     
- Misses      26161    26190      +29     
- Partials      924      925       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

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

A wedged/contended vmclock seqlock makes the bounded seqlock reader give
up and report failure. Previously CRYPTO_get_vm_ube_generation returned 0
for this, and ube.c mapped any 0 to ube_failed() -- which is
CRYPTO_once-guarded and irreversibly disables ALL UBE detection (fork
included) for the entire process. So a single momentary read failure
permanently stopped reseeding.

Make CRYPTO_get_vm_ube_generation tri-state: 1 success, 0 permanent init
failure, -1 transient read failure. ube.c now treats -1 as "reseed
conservatively for this call" (return 0 to the DRBG) without calling
ube_failed(); only a permanent 0 disables detection. DRBG consumers are
unchanged -- they still reseed on a 0 return from
CRYPTO_get_ube_generation_number.
The legacy build (tests/ci/run_legacy_build.sh) compiles with gcc 4.1
under -std=gnu99, which predates C11 <stdatomic.h> and the __atomic
builtins, so the unconditional include failed with "stdatomic.h: No such
file or directory".

Wrap the seqlock acquire fence in vm_ube_acquire_fence(), gated on the
same C11 check the tree already uses for refcounting (crypto/internal.h):
use atomic_thread_fence(memory_order_acquire) when C11 atomics are
available, and fall back to __sync_synchronize() -- a full barrier
available since gcc 4.1 -- otherwise. A full barrier is stronger than the
acquire we need, so it is always correct.
HAZMAT_init_vmclock_file unconditionally rewrote the stand-in device
file at every process startup, resetting vm_generation_counter to 0.
all_tests.go runs several crypto_test processes concurrently against the
same file, so a process starting up would reset the counter in the
middle of another process's VmUbeGenerationTest -- which had just written
e.g. 42 and was about to read it back -- producing a consistent read of
0. This surfaced as a flaky "Expected 42, Which is 0" on slower/older CI
hosts (centos:7, ubuntu:16.04, fedora:31) where startup timing overlapped
differently; it was a cross-process data race, not a compiler issue.

Only initialize the file when it does not already contain a valid vmclock
(magic not set), mirroring HAZMAT_init_sysgenid_file's populate-once
intent. Gate on the magic rather than emptiness because CI pre-fills the
file zeroed (dd), so it is never empty and vmclock additionally requires
magic to be set.
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.

2 participants