use std::fmt;
use nix::errno::Errno;
pub(crate) const PR_GET_SPECULATION_CTRL: libc::c_int = 52;
pub(crate) const PR_SET_SPECULATION_CTRL: libc::c_int = 53;
pub(crate) const PR_SPEC_STORE_BYPASS: u32 = 0;
pub(crate) const PR_SPEC_INDIRECT_BRANCH: u32 = 1;
pub(crate) const PR_SPEC_L1D_FLUSH: u32 = 2;
pub(crate) const SPECULATION_CTRL_MASK: u32 = 0x1F;
pub(crate) const PR_SPEC_NOT_AFFECTED: u32 = 0;
pub(crate) const PR_SPEC_PRCTL: u32 = 1 << 0;
pub(crate) const PR_SPEC_ENABLE: u32 = 1 << 1;
pub(crate) const PR_SPEC_DISABLE: u32 = 1 << 2;
pub(crate) const PR_SPEC_FORCE_DISABLE: u32 = 1 << 3;
pub(crate) const PR_SPEC_DISABLE_NOEXEC: u32 = 1 << 4;
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpeculationFeature {
StoreBypass = PR_SPEC_STORE_BYPASS,
IndirectBranch = PR_SPEC_INDIRECT_BRANCH,
L1DFlush = PR_SPEC_L1D_FLUSH,
}
impl fmt::Display for SpeculationFeature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let feature_str = match self {
SpeculationFeature::StoreBypass => "Store Bypass",
SpeculationFeature::IndirectBranch => "Indirect Branch",
SpeculationFeature::L1DFlush => "L1D Flush",
};
write!(f, "{feature_str}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SpeculationStatus(pub u32);
impl SpeculationStatus {
pub fn from_raw(val: u32) -> Self {
SpeculationStatus(val & SPECULATION_CTRL_MASK)
}
pub fn is_not_affected(&self) -> bool {
self.0 == PR_SPEC_NOT_AFFECTED
}
pub fn can_prctl_set(&self) -> bool {
self.0 & PR_SPEC_PRCTL != 0
}
pub fn is_enabled(&self) -> bool {
self.0 & PR_SPEC_ENABLE != 0
}
pub fn is_disabled(&self) -> bool {
self.0 & PR_SPEC_DISABLE != 0
}
pub fn is_force_disabled(&self) -> bool {
self.0 & PR_SPEC_FORCE_DISABLE != 0
}
pub fn is_disable_noexec(&self) -> bool {
self.0 & PR_SPEC_DISABLE_NOEXEC != 0
}
pub fn raw(&self) -> u32 {
self.0
}
}
impl fmt::Display for SpeculationStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_not_affected() {
return write!(f, "Not affected by speculation");
}
let mut statuses = Vec::new();
if self.is_enabled() {
statuses.push("Speculation feature is enabled, mitigation is disabled");
} else if self.is_disabled() {
statuses.push("Speculation feature is disabled, mitigation is enabled");
} else if self.is_force_disabled() {
statuses.push("Speculation feature is force-disabled, mitigation is enabled");
} else if self.is_disable_noexec() {
statuses.push("Speculation feature is exec-disabled, mitigation is enabled");
}
if self.can_prctl_set() {
statuses.push("(prctl can set speculation mitigation)");
}
if statuses.is_empty() {
write!(f, "Speculation feature status unknown: {:#X}", self.0)
} else {
write!(f, "{}.", statuses.join(" "))
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SpeculationControlStatus {
pub feature: SpeculationFeature,
pub status: SpeculationStatus,
}
impl fmt::Display for SpeculationControlStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} status: {}", self.feature, self.status)
}
}
pub fn speculation_get(feature: SpeculationFeature) -> Result<SpeculationControlStatus, Errno> {
let ret = Errno::result(unsafe {
libc::prctl(PR_GET_SPECULATION_CTRL, feature as libc::c_int, 0, 0, 0)
})?;
#[expect(clippy::cast_sign_loss)]
let masked = (ret as u32) & SPECULATION_CTRL_MASK;
let status = SpeculationStatus::from_raw(masked);
Ok(SpeculationControlStatus { feature, status })
}
pub fn speculation_set(
feature: SpeculationFeature,
status: SpeculationStatus,
) -> Result<(), Errno> {
#[expect(clippy::cast_lossless)]
Errno::result(unsafe {
libc::prctl(
PR_SET_SPECULATION_CTRL,
feature as libc::c_int,
status.raw() as libc::c_ulong,
0,
0,
)
})
.map(drop)
}