Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ members = [
"integration",
"qos_bridge",
"qos_client",
"qos_clock",
"qos_core",
"qos_crypto",
"qos_host",
Expand All @@ -21,6 +22,7 @@ default-members = [
"integration",
"qos_bridge",
"qos_client",
"qos_clock",
"qos_core",
"qos_crypto",
"qos_host",
Expand Down Expand Up @@ -103,6 +105,7 @@ zeroize = { version = "1.8", default-features = false }
integration = { path = "integration" }
qos_host = { path = "qos_host", default-features = false }
qos_client = { path = "qos_client", version = "0.6.1", default-features = false }
qos_clock = { path = "qos_clock", version = "0.6.1", default-features = false }
qos_core = { path = "qos_core", version = "0.6.1", default-features = false }
qos_crypto = { path = "qos_crypto", version = "0.6.1", default-features = false }
qos_hex = { path = "qos_hex", version = "0.6.1", default-features = false }
Expand Down
23 changes: 22 additions & 1 deletion src/qos_aws/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use qos_system::{check_hwrng, dmesg, poweroff};
use qos_system::{
check_clocksource, check_hwrng, detect_ptp_clock_device, dmesg, poweroff,
};

/// Signal to Nitro hypervisor that booting was successful
fn nitro_heartbeat() {
Expand Down Expand Up @@ -38,4 +40,23 @@ pub fn init_platform() {
poweroff();
}
};

match check_clocksource("kvm-clock") {
Ok(()) => dmesg("Validated clock source is kvm-clock".to_string()),
Err(e) => {
eprintln!("{}", e);
dmesg(format!("Clock source validation failed: {}", e.message));
poweroff();
}
};

match detect_ptp_clock_device() {
Some(path) => dmesg(format!(
"PTP hardware clock device is visible at {path}; synchronization is not configured by init"
)),
None => dmesg(
"No PTP hardware clock device is exposed to the enclave; synchronization is not configured by init"
.to_string(),
),
}
}
18 changes: 18 additions & 0 deletions src/qos_clock/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "qos_clock"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
description = "Clock abstractions for QuorumOS"
repository = "https://github.com/tkhq/qos"
keywords = ["quorumos", "clock", "time"]
categories = ["os"]

[lints]
workspace = true

[dependencies]

[target.'cfg(target_os = "linux")'.dependencies]
qos_system = { path = "../qos_system" }
57 changes: 57 additions & 0 deletions src/qos_clock/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//! Wall-clock access for Unix time inside QOS.

use std::{fmt, time::SystemTime};

/// Error returned when reading wall-clock time fails or overflows `u64`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WallClockError {
/// Converting between time units failed.
TimeConversionFailed,
/// The system clock was before the Unix epoch.
TimeBeforeUnixEpoch,
/// The returned value did not fit in `u64`.
TimeOverflow,
}

impl fmt::Display for WallClockError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TimeConversionFailed => {
write!(f, "system clock conversion failed")
}
Self::TimeBeforeUnixEpoch => {
write!(f, "system clock was before the Unix epoch")
}
Self::TimeOverflow => {
write!(f, "system clock value overflowed u64")
}
}
}
}

/// Source of Unix wall-clock time for expiry and freshness checks.
pub trait WallClock: Send + Sync {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Realistically we could get around needing a trait and just use SystemTime::now().duration_since(SystemTime::UNIX_EPOCH). But trait might be useful for testing when we want to control time

/// Returns Unix time in whole milliseconds.
fn unix_time_millis(&self) -> Result<u64, WallClockError>;

/// Returns Unix time in whole seconds.
fn unix_time_seconds(&self) -> Result<u64, WallClockError> {
self.unix_time_millis()?
.checked_div(1_000)
.ok_or(WallClockError::TimeConversionFailed)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The default implementation for SystemTime is inefficient... it's already a u64
https://doc.rust-lang.org/stable/src/core/time.rs.html#506

}

/// Wall clock backed by the host operating system.
pub struct SystemWallClock;

impl WallClock for SystemWallClock {
fn unix_time_millis(&self) -> Result<u64, WallClockError> {
let duration = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|_| WallClockError::TimeBeforeUnixEpoch)?;

u64::try_from(duration.as_millis())
.map_err(|_| WallClockError::TimeOverflow)
}
}
42 changes: 42 additions & 0 deletions src/qos_system/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ use std::{

use libc::{c_int, c_ulong, c_void};

const HWRNG_CURRENT_PATH: &str = "/sys/class/misc/hw_random/rng_current";
const CLOCKSOURCE_CURRENT_PATH: &str =
"/sys/devices/system/clocksource/clocksource0/current_clocksource";

#[derive(Debug)]
pub struct SystemError {
pub message: String,
Expand Down Expand Up @@ -168,6 +172,44 @@ pub fn check_hwrng(rng_expected: &str) -> Result<(), SystemError> {
Ok(())
}

fn read_trimmed(path: &str) -> Result<String, SystemError> {
use std::fs::read_to_string;

read_to_string(path)
.map(|value| value.trim().to_string())
.map_err(|_| SystemError { message: format!("Failed to read {path}") })
}

/// Verify the expected clock source is active.
pub fn check_clocksource(clock_expected: &str) -> Result<(), SystemError> {
let clock_current = read_trimmed(CLOCKSOURCE_CURRENT_PATH)?;
if clock_expected != clock_current {
return Err(SystemError {
message: format!(
"Clock source was {} instead of {}",
clock_current, clock_expected
),
});
}

Ok(())
}

/// Detect whether a PTP hardware clock device is exposed in the enclave.
///
/// AWS documents both `/dev/ptp<index>` and the stable `/dev/ptp_ena`
/// symlink for supported instances:
/// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configure-ec2-ntp.html
pub fn detect_ptp_clock_device() -> Option<&'static str> {
for path in ["/dev/ptp_ena", "/dev/ptp0"] {
if File::open(path).is_ok() {
return Some(path);
}
}

None
}

#[cfg(target_env = "musl")]
type IoctlNumType = ::libc::c_int;
#[cfg(not(target_env = "musl"))]
Expand Down
Loading