#![allow(dead_code)]
use anyhow::Result;
use kvm_ioctls::VcpuFd;
use super::msr_indices::MSR_LSTAR;
use super::msr_io::read_one_msr;
const PHYSICAL_ALIGN: u64 = 2 * 1024 * 1024;
pub(crate) const KERNEL_HALF_CANONICAL_4LEVEL: u64 = 0xFFFF_8000_0000_0000;
#[derive(Debug)]
#[non_exhaustive]
pub enum LstarDeriveError {
LstarUnsupported,
LstarZero,
LinkUnknown,
NonCanonical { lstar: u64 },
LinkAboveLstar { lstar: u64, link: u64 },
Misaligned { offset: u64 },
}
impl std::fmt::Display for LstarDeriveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::LstarUnsupported => write!(
f,
"KVM_GET_MSRS returned 0 entries for MSR_LSTAR; host KVM \
build doesn't expose long-mode SYSCALL MSRs"
),
Self::LstarZero => write!(
f,
"MSR_LSTAR == 0; guest may not have reached idt_syscall_init, \
or CONFIG_X86_FRED=y kernel skipped the LSTAR write"
),
Self::LinkUnknown => write!(
f,
"entry_syscall_64_link_kva == 0; vmlinux lacks the \
entry_SYSCALL_64 symbol — callers must check \
KernelSymbols::entry_syscall_64_kva is Some before calling"
),
Self::NonCanonical { lstar } => write!(
f,
"MSR_LSTAR = {lstar:#x}; not in kernel-half canonical range \
(expected bits 63..47 all set, ≥ {KERNEL_HALF_CANONICAL_4LEVEL:#x})"
),
Self::LinkAboveLstar { lstar, link } => write!(
f,
"MSR_LSTAR = {lstar:#x} < entry_SYSCALL_64_link = {link:#x}; \
wrong vmlinux for the running guest kernel"
),
Self::Misaligned { offset } => write!(
f,
"derived virt_kaslr_offset = {offset:#x} not aligned to \
PHYSICAL_ALIGN ({PHYSICAL_ALIGN:#x}); inputs disagree on \
the loaded kernel image"
),
}
}
}
impl std::error::Error for LstarDeriveError {}
impl LstarDeriveError {
pub fn is_retryable(&self) -> bool {
matches!(
self,
Self::LstarUnsupported | Self::LstarZero | Self::NonCanonical { .. }
)
}
}
pub fn derive_virt_kaslr(lstar: u64, entry_syscall_64_link: u64) -> Result<u64, LstarDeriveError> {
if lstar == 0 {
return Err(LstarDeriveError::LstarZero);
}
if entry_syscall_64_link == 0 {
return Err(LstarDeriveError::LinkUnknown);
}
if lstar < KERNEL_HALF_CANONICAL_4LEVEL {
return Err(LstarDeriveError::NonCanonical { lstar });
}
if lstar < entry_syscall_64_link {
return Err(LstarDeriveError::LinkAboveLstar {
lstar,
link: entry_syscall_64_link,
});
}
let offset = lstar - entry_syscall_64_link;
if offset & (PHYSICAL_ALIGN - 1) != 0 {
return Err(LstarDeriveError::Misaligned { offset });
}
Ok(offset)
}
pub(crate) fn read_and_derive(vcpu: &VcpuFd, entry_syscall_64_link: u64) -> Result<u64> {
let lstar = read_one_msr(vcpu, MSR_LSTAR)?.ok_or_else(|| {
anyhow::anyhow!(
"derive virt_kaslr_offset: {}",
LstarDeriveError::LstarUnsupported
)
})?;
derive_virt_kaslr(lstar, entry_syscall_64_link)
.map_err(|e| anyhow::anyhow!("derive virt_kaslr_offset: {e}"))
}
#[cfg(test)]
mod tests {
use super::*;
const LINK_KVA_TYPICAL: u64 = 0xFFFF_FFFF_8100_0000;
fn aligned_lstar(offset_mb: u64) -> u64 {
LINK_KVA_TYPICAL + (offset_mb * 1024 * 1024)
}
#[test]
fn derive_happy_path() {
let lstar = aligned_lstar(20); let offset = derive_virt_kaslr(lstar, LINK_KVA_TYPICAL).expect("derive succeeds");
assert_eq!(offset, 20 * 1024 * 1024);
}
#[test]
fn derive_zero_offset_is_valid() {
let offset =
derive_virt_kaslr(LINK_KVA_TYPICAL, LINK_KVA_TYPICAL).expect("zero-offset derive ok");
assert_eq!(offset, 0);
}
#[test]
fn derive_lstar_zero_fred_or_early_boot() {
let err =
derive_virt_kaslr(0, LINK_KVA_TYPICAL).expect_err("zero LSTAR must produce LstarZero");
assert!(matches!(err, LstarDeriveError::LstarZero));
}
#[test]
fn derive_non_canonical_below_high_half() {
let bogus = 0x0000_FFFF_8100_0000u64;
let err = derive_virt_kaslr(bogus, LINK_KVA_TYPICAL).expect_err("non-canonical must fail");
assert!(matches!(err, LstarDeriveError::NonCanonical { lstar } if lstar == bogus));
}
#[test]
fn derive_link_above_lstar_when_link_too_high() {
let lstar = LINK_KVA_TYPICAL - (4 * 1024 * 1024);
let err = derive_virt_kaslr(lstar, LINK_KVA_TYPICAL).expect_err("lstar < link must error");
assert!(matches!(
err,
LstarDeriveError::LinkAboveLstar { lstar: l, link: k }
if l == lstar && k == LINK_KVA_TYPICAL
));
}
#[test]
fn derive_at_canonical_threshold_uses_link_guard() {
let err = derive_virt_kaslr(KERNEL_HALF_CANONICAL_4LEVEL, LINK_KVA_TYPICAL)
.expect_err("lstar < link must produce LinkAboveLstar");
assert!(matches!(err, LstarDeriveError::LinkAboveLstar { .. }));
}
#[test]
fn derive_minimum_aligned_offset() {
let offset = derive_virt_kaslr(aligned_lstar(2), LINK_KVA_TYPICAL)
.expect("2 MiB offset is the minimum valid nonzero");
assert_eq!(offset, 2 * 1024 * 1024);
}
#[test]
fn derive_lstar_max_u64_falls_to_misaligned() {
let err = derive_virt_kaslr(u64::MAX, LINK_KVA_TYPICAL)
.expect_err("u64::MAX produces misaligned offset");
assert!(matches!(err, LstarDeriveError::Misaligned { .. }));
}
#[test]
fn derive_errors_render_diagnostic_payload() {
let lstar_zero = format!("{}", LstarDeriveError::LstarZero);
assert!(lstar_zero.contains("MSR_LSTAR == 0"));
let non_canonical = format!(
"{}",
LstarDeriveError::NonCanonical {
lstar: 0x1234_5678_9abc_def0
}
);
assert!(non_canonical.contains("0x1234"));
let link_above = format!(
"{}",
LstarDeriveError::LinkAboveLstar {
lstar: 0x1,
link: 0x2
}
);
assert!(link_above.contains("0x1") && link_above.contains("0x2"));
let misaligned = format!(
"{}",
LstarDeriveError::Misaligned {
offset: 0xdead_beef
}
);
assert!(misaligned.contains("0xdeadbeef"));
}
#[test]
fn is_retryable_partitions_variants_correctly() {
assert!(LstarDeriveError::LstarUnsupported.is_retryable());
assert!(LstarDeriveError::LstarZero.is_retryable());
assert!(LstarDeriveError::NonCanonical { lstar: 0 }.is_retryable());
assert!(!LstarDeriveError::LinkUnknown.is_retryable());
assert!(!LstarDeriveError::LinkAboveLstar { lstar: 0, link: 0 }.is_retryable());
assert!(!LstarDeriveError::Misaligned { offset: 0 }.is_retryable());
}
#[test]
fn derive_link_unknown_when_link_is_zero() {
let err =
derive_virt_kaslr(aligned_lstar(8), 0).expect_err("link == 0 must error LinkUnknown");
assert!(matches!(err, LstarDeriveError::LinkUnknown));
}
#[test]
fn derive_misalignment_rejected() {
let lstar = LINK_KVA_TYPICAL + (20 * 1024 * 1024) + 4096;
let err = derive_virt_kaslr(lstar, LINK_KVA_TYPICAL).expect_err("misalign must fail");
assert!(matches!(err, LstarDeriveError::Misaligned { offset }
if offset == (20 * 1024 * 1024) + 4096));
}
}