store: a boot record whose epoch can be trusted, not just read

The record the kernel writes about its own boot carried `boot=<epoch>` and nothing else — a
reading taken at the first write of the boot, which is exactly when this machine's clock is
least likely to be right: it arrives from a night powered off tens of seconds out, and on the
boot before it, 10h 27m behind. Nothing in the record said the time was provisional.

Two fields carry the account now. `uptime` comes from the monotonic clock, so `boot - uptime`
is the instant the boot began whatever the wall clock was doing. `clock=raw|set` says whether
the reading predates a correction. And the kernel acts on the difference: the wall clock can be
set from anywhere and the monotonic clock cannot be set at all, so a wall clock that moves
without it is a correction and nothing else — when that is observed, the next write rewrites
the record at the same coordinate with the corrected time, bounded like the first write.

Measured in the guest (verify-boot-record, which now moves the clock forward an hour mid-boot):
boot=1790308025 uptime=3 clock=raw, then the same coordinate at boot=1790311625 uptime=3
clock=set — exactly the hour that was moved. The gate also refuses a record whose epoch
precedes its own uptime, and one that claims to have been written long after the boot began.

The retry half of this was already in the tree (a count rather than a swap that cannot
un-claim itself, from e968b3964); this is the epoch half, and the record's own "Next" list
named both.
This commit is contained in:
CUBELinux
2026-09-25 00:09:09 -04:00
parent 8c24a7ff0d
commit 3d5d213c75
+140 -18
View File
@@ -41,7 +41,7 @@
//! ```
use core::fmt::{self, Write};
use core::sync::atomic::{AtomicU32, Ordering};
use core::sync::atomic::{AtomicBool, AtomicI64, AtomicU32, Ordering};
// The store's format, in one file, shared with userspace.
//
@@ -302,8 +302,6 @@ const BOOT_FLAGS: u16 = 1 << 7;
/// needs. History would be a second record per boot, and nobody has asked for one.
const BOOT_POINT: (u64, u64, u64) = (0, 0, 0);
/// Whether this boot has already been recorded. One attempt, claimed with a swap so two callers
/// racing into their first write cannot write two records.
/// How many attempts this boot has made at recording itself, and the ceiling on them.
///
/// A count rather than a flag, because a flag that is claimed *before* the write cannot un-claim
@@ -316,6 +314,28 @@ const BOOT_POINT: (u64, u64, u64) = (0, 0, 0);
static BOOT_RECORD_ATTEMPTS: AtomicU32 = AtomicU32::new(0);
const BOOT_RECORD_MAX_ATTEMPTS: u32 = 4;
/// The wall clock and the monotonic clock as they stood when this boot's record was written.
///
/// Kept so a later write can tell whether the *epoch* in that record is still the truth. The record
/// is written at the first write of a boot, which is exactly when the wall clock is least likely to
/// have been corrected: this box arrives from a night powered off with a clock tens of seconds out,
/// so the first record carried a time it had no way to qualify. Two clocks are the whole mechanism,
/// and neither needs a reference: the wall clock can be set from anywhere, the monotonic clock
/// cannot be set at all, so a divergence between them over the same interval is a clock correction
/// and nothing else. `uptime` in the record comes from the monotonic side, which means the instant
/// the boot began (`boot - uptime`) stays recoverable however wrong the wall clock was.
static BOOT_RECORD_WALL: AtomicI64 = AtomicI64::new(0);
static BOOT_RECORD_MONO: AtomicI64 = AtomicI64::new(0);
/// Whether the record has been rewritten after a correction, and how many tries that has taken.
static BOOT_RECORD_CLOCK_SET: AtomicBool = AtomicBool::new(false);
static BOOT_RECORD_REFRESHES: AtomicU32 = AtomicU32::new(0);
const BOOT_RECORD_MAX_REFRESHES: u32 = 2;
/// How far the wall clock must move relative to the monotonic clock before it counts as corrected.
/// Both are whole seconds read one after the other, so a second of slack belongs here.
const CLOCK_SET_TOLERANCE_SECS: i64 = 2;
/// FNV-1a offset basis.
const FNV_OFFSET: u64 = cube_format::FNV_OFFSET;
@@ -1972,24 +1992,116 @@ fn c_len(p: *const u8, max: usize) -> usize {
n
}
/// Which clock the epoch in a boot record was read from.
///
/// `Raw` is the reading the first write of the boot took, before anything has had a chance to
/// correct the clock. `Set` is a reading taken after a correction was *observed*, which is the only
/// way this record can claim a time it knows was not provisional.
#[derive(Clone, Copy)]
enum ClockState {
Raw,
Set,
}
impl ClockState {
fn word(self) -> &'static [u8] {
match self {
ClockState::Raw => b"raw",
ClockState::Set => b"set",
}
}
}
/// Both clocks, read as close together as two calls can be.
fn now_clocks() -> (i64, i64) {
let mut wall = bindings::timespec64 {
tv_sec: 0,
tv_nsec: 0,
};
let mut mono = bindings::timespec64 {
tv_sec: 0,
tv_nsec: 0,
};
// SAFETY: both are live, writable `timespec64`s, and neither call retains a pointer to one.
unsafe { bindings::ktime_get_real_ts64(&mut wall) };
// SAFETY: as above.
unsafe { bindings::ktime_get_ts64(&mut mono) };
(wall.tv_sec as i64, mono.tv_sec as i64)
}
/// Whether the wall clock has been set or adjusted since this boot's record was written.
///
/// The wall clock can be set from anywhere and the monotonic clock cannot be set at all, so if the
/// wall clock has moved by a different amount than the monotonic clock over the same interval, then
/// something set the wall clock — and the epoch in the record is a reading from before it.
fn wall_clock_was_set() -> bool {
let wall_at = BOOT_RECORD_WALL.load(Ordering::Acquire);
if wall_at == 0 {
return false;
}
let mono_at = BOOT_RECORD_MONO.load(Ordering::Acquire);
let (wall, mono) = now_clocks();
((wall - wall_at) - (mono - mono_at)).abs() > CLOCK_SET_TOLERANCE_SECS
}
/// Rewrite this boot's record when the clock it was read from has since been corrected.
///
/// The record lives at one coordinate and each boot overwrites the last, so this is the same write
/// the first one was, with a clock reading that is no longer provisional. It is bounded like the
/// first: a store that will not take the rewrite logs and stops asking.
fn refresh_boot_record_if_the_clock_was_set() {
if BOOT_RECORD_CLOCK_SET.load(Ordering::Acquire) || !wall_clock_was_set() {
return;
}
if BOOT_RECORD_REFRESHES.fetch_add(1, Ordering::AcqRel) >= BOOT_RECORD_MAX_REFRESHES {
return;
}
let (wall, mono) = now_clocks();
let value = match boot_record_value(ClockState::Set, wall, mono) {
Some(v) => v,
None => return,
};
let mutation = Mutation {
space: BOOT_SPACE,
key: morton_encode(BOOT_POINT.0, BOOT_POINT.1, BOOT_POINT.2),
flags: BOOT_FLAGS,
value,
};
match append_mutation(&mutation, 1) {
Ok(_) => {
BOOT_RECORD_CLOCK_SET.store(true, Ordering::Release);
BOOT_RECORD_WALL.store(wall, Ordering::Release);
BOOT_RECORD_MONO.store(mono, Ordering::Release);
pr_info!("cubelinux: the clock was set, so this boot's record was rewritten with the corrected time\n");
}
Err(_) => pr_warn!("cubelinux: could not rewrite this boot's record after a clock correction\n"),
}
}
/// The kernel's own account of the boot it is having, as one line:
/// `boot=<seconds since the epoch> device=<the store it resolved> kernel=<its version banner>`.
/// `boot=<seconds since the epoch> uptime=<seconds since boot> clock=<raw|set> device=<the store it
/// resolved> kernel=<its version banner>`.
///
/// The banner is last and unquoted because it contains spaces, so everything after the final `=`
/// is the kernel's own words rather than a field this code parsed. The time is raw epoch seconds:
/// rendering a calendar date in the kernel is date arithmetic, and a caller with a clock can do it
/// without a kernel bug being the reason a timestamp is wrong.
fn boot_record_value() -> Option<KVVec<u8>> {
///
/// `uptime` is carried beside it because the epoch alone does not say whether it was read at the
/// start of the boot or an hour into it, and because `boot - uptime` is the instant the boot began
/// irrespective of what the wall clock was doing. `clock` says which of the two readings this is —
/// see [`ClockState`]. A reader that wants a time it can trust takes `clock=set`; a reader that
/// wants the boot instant takes `boot - uptime`; a reader that wants both takes them from the same
/// line and does not have to guess which one it got.
fn boot_record_value(clock: ClockState, wall: i64, mono: i64) -> Option<KVVec<u8>> {
let mut out = KVVec::<u8>::new();
let mut ts = bindings::timespec64 {
tv_sec: 0,
tv_nsec: 0,
};
// SAFETY: `ts` is a live, writable `timespec64`, and the call retains no pointer to it.
unsafe { bindings::ktime_get_real_ts64(&mut ts) };
push(&mut out, b"boot=")?;
push_dec(&mut out, ts.tv_sec as u64)?;
push_dec(&mut out, wall.max(0) as u64)?;
push(&mut out, b" uptime=")?;
push_dec(&mut out, mono.max(0) as u64)?;
push(&mut out, b" clock=")?;
push(&mut out, clock.word())?;
push(&mut out, b" device=")?;
// SAFETY: `store_device` returns a static NUL-terminated buffer, valid for the life of the
@@ -2042,14 +2154,20 @@ fn ensure_boot_record() {
// cannot both write a record: the loser returns, exactly as the swap did. The winner's attempt
// is counted whether it succeeds or fails, which is what makes a failure retryable.
let made = BOOT_RECORD_ATTEMPTS.load(Ordering::Acquire);
if made >= BOOT_RECORD_MAX_ATTEMPTS
|| BOOT_RECORD_ATTEMPTS
.compare_exchange(made, made + 1, Ordering::AcqRel, Ordering::Acquire)
.is_err()
if made >= BOOT_RECORD_MAX_ATTEMPTS {
// The record is in the store. The last thing that can make it untrue is the clock it was
// read from, so that is the only thing still checked from here on.
refresh_boot_record_if_the_clock_was_set();
return;
}
if BOOT_RECORD_ATTEMPTS
.compare_exchange(made, made + 1, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
return;
}
let value = match boot_record_value() {
let (wall, mono) = now_clocks();
let value = match boot_record_value(ClockState::Raw, wall, mono) {
Some(v) => v,
None => {
pr_warn!("cubelinux: not enough memory to build this boot's record\n");
@@ -2066,7 +2184,11 @@ fn ensure_boot_record() {
// log's head, not the image.
match append_mutation(&mutation, 1) {
Ok(_) => {
// Recorded. Park the count at the ceiling so no later write asks again.
// Recorded, with the two clock readings that go with it, so a later write can tell
// whether the epoch it just wrote is still the truth. Park the count at the ceiling so
// no later write asks again.
BOOT_RECORD_WALL.store(wall, Ordering::Release);
BOOT_RECORD_MONO.store(mono, Ordering::Release);
BOOT_RECORD_ATTEMPTS.store(BOOT_RECORD_MAX_ATTEMPTS, Ordering::Release);
pr_info!("cubelinux: recorded this boot in the store\n");
}