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)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prctl_speculation_1() {
assert_eq!(PR_GET_SPECULATION_CTRL, 52);
assert_eq!(PR_SET_SPECULATION_CTRL, 53);
assert_eq!(PR_SPEC_STORE_BYPASS, 0);
assert_eq!(PR_SPEC_INDIRECT_BRANCH, 1);
assert_eq!(PR_SPEC_L1D_FLUSH, 2);
assert_eq!(SPECULATION_CTRL_MASK, 0x1F);
assert_eq!(PR_SPEC_NOT_AFFECTED, 0);
assert_eq!(PR_SPEC_PRCTL, 1);
assert_eq!(PR_SPEC_ENABLE, 2);
assert_eq!(PR_SPEC_DISABLE, 4);
assert_eq!(PR_SPEC_FORCE_DISABLE, 8);
assert_eq!(PR_SPEC_DISABLE_NOEXEC, 16);
}
#[test]
fn test_speculation_feature_1() {
assert_eq!(
format!("{}", SpeculationFeature::StoreBypass),
"Store Bypass"
);
}
#[test]
fn test_speculation_feature_2() {
assert_eq!(
format!("{}", SpeculationFeature::IndirectBranch),
"Indirect Branch"
);
}
#[test]
fn test_speculation_feature_3() {
assert_eq!(format!("{}", SpeculationFeature::L1DFlush), "L1D Flush");
}
#[test]
fn test_speculation_feature_4() {
let _ = speculation_get(SpeculationFeature::StoreBypass);
}
#[test]
fn test_speculation_feature_5() {
let _ = speculation_get(SpeculationFeature::IndirectBranch);
}
#[test]
fn test_speculation_feature_6() {
let _ = speculation_get(SpeculationFeature::L1DFlush);
}
#[test]
fn test_speculation_status_1() {
let status = SpeculationStatus::from_raw(0xFFFF_FFFF);
assert_eq!(status.raw(), SPECULATION_CTRL_MASK);
}
#[test]
fn test_speculation_status_2() {
let status = SpeculationStatus::from_raw(PR_SPEC_PRCTL | PR_SPEC_ENABLE);
assert_eq!(status.raw(), PR_SPEC_PRCTL | PR_SPEC_ENABLE);
}
#[test]
fn test_speculation_status_3() {
let status = SpeculationStatus::from_raw(0);
assert!(status.is_not_affected());
assert!(!status.can_prctl_set());
assert!(!status.is_enabled());
assert!(!status.is_disabled());
assert!(!status.is_force_disabled());
assert!(!status.is_disable_noexec());
}
#[test]
fn test_speculation_status_4() {
let status = SpeculationStatus::from_raw(PR_SPEC_PRCTL);
assert!(!status.is_not_affected());
assert!(status.can_prctl_set());
}
#[test]
fn test_speculation_status_5() {
let status = SpeculationStatus::from_raw(PR_SPEC_ENABLE);
assert!(status.is_enabled());
assert!(!status.is_disabled());
}
#[test]
fn test_speculation_status_6() {
let status = SpeculationStatus::from_raw(PR_SPEC_DISABLE);
assert!(status.is_disabled());
assert!(!status.is_enabled());
}
#[test]
fn test_speculation_status_7() {
let status = SpeculationStatus::from_raw(PR_SPEC_FORCE_DISABLE);
assert!(status.is_force_disabled());
}
#[test]
fn test_speculation_status_8() {
let status = SpeculationStatus::from_raw(PR_SPEC_DISABLE_NOEXEC);
assert!(status.is_disable_noexec());
}
#[test]
fn test_speculation_status_9() {
let status = SpeculationStatus::from_raw(0);
assert_eq!(format!("{status}"), "Not affected by speculation");
}
#[test]
fn test_speculation_status_10() {
let status = SpeculationStatus::from_raw(PR_SPEC_ENABLE);
let display = format!("{status}");
assert!(display.contains("enabled, mitigation is disabled"));
}
#[test]
fn test_speculation_status_11() {
let status = SpeculationStatus::from_raw(PR_SPEC_DISABLE);
let display = format!("{status}");
assert!(display.contains("disabled, mitigation is enabled"));
}
#[test]
fn test_speculation_status_12() {
let status = SpeculationStatus::from_raw(PR_SPEC_FORCE_DISABLE);
let display = format!("{status}");
assert!(display.contains("force-disabled"));
}
#[test]
fn test_speculation_status_13() {
let status = SpeculationStatus::from_raw(PR_SPEC_DISABLE_NOEXEC);
let display = format!("{status}");
assert!(display.contains("exec-disabled"));
}
#[test]
fn test_speculation_status_14() {
let status = SpeculationStatus::from_raw(PR_SPEC_PRCTL);
let display = format!("{status}");
assert!(display.contains("prctl can set"));
}
#[test]
fn test_speculation_status_15() {
let status = SpeculationStatus::from_raw(PR_SPEC_ENABLE | PR_SPEC_PRCTL);
let display = format!("{status}");
assert!(display.contains("enabled"));
assert!(display.contains("prctl"));
}
#[test]
fn test_speculation_control_status_1() {
let cs = SpeculationControlStatus {
feature: SpeculationFeature::StoreBypass,
status: SpeculationStatus::from_raw(0),
};
let display = format!("{cs}");
assert!(display.contains("Store Bypass"));
assert!(display.contains("Not affected"));
}
}